The purpose of this document is to lay out clearly how the Comstack Private Messaging API can be interacted with so resources can be consumed, created and deleted.
Anywhere you see the term "RESTful" take that with a pinch of salt as in truth this API isn't restful, more is than not but certain things are present to make this API easier to work with.
I've flat out copied things I like from the following sources:
401
response.application/json
.JSON
, if not you can read up here: https://www.json.comThe API will live at /api/v1/cs-pm
on the installed site.
Use HTTP verbs to operate on the collections and elements.
GET
- Used to access data, this API will not allow modification to happen over GET
.
POST
- Create a resource.
PUT
- Perform an action on a resource.
PATCH
- Partially update a resource.
DELETE
- Delete resources or undo an action.
If an error occurs when operating the REST endpoint via URL, A valid JSON object with code
, message
and description
would be returned.
Here's an example...
{ "status": 400, "title": "The sort wrong_key is not allowed for this path.", "detail": Bad Request.', "type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1" }
When returning a response a HTTP code will always be used (duh). These have been clubbed together from various sources.
Rate limiting won't be considered for this API at this time but should be thought of at some point to prevent abuse. As this API requires authentication this mitigates things in a minor way.
Providing a response provides a body, list sets are always returned with a data
property. Data items will always have type
and id
properties.
Along with a data
property there will also be count
and if enough results paging.
You can filter a result set on an endpoint by a property that belongs to the model being listed.
This follows the same format as laid out by the RESTful Drupal module.
Reducing a result set by a single field, in this case where the user models property name
has the value alli
.
/api/v1/cs-pm/users/available-users?filter[name]=alli
Reducing a result set by a field and specifying the operator.
Valid operators are "="
, <
, >
, "<="
, ">="
, "<>"
, "!="
, BETWEEN
, CONTAINS
(default), IN
, LIKE
, NOT IN
, STARTS_WITH
. Yes, the text is uppercase shouty, note that certain operators are wrapped in quote marks.
/api/v1/cs-pm/users/available-users?filter[name][value]=a&filter[name][operator]=STARTS_WITH
You can do multiple fields, multiple filters on a single field, lots!
As with filtering you can also sort on any data model property, do this by adding sort
to the request query string.
The default direction for a sort is Ascending
, if you prepend the property name with a minus -
it will change the sort to Descending
.
Example time!
/api/v1/cs-pm/users/available-users?sort=id
/api/v1/cs-pm/users/available-users?sort=-id
You can sort on multiple fields by separating things with a comma in the single sort
query string parameter. Unlike with filtering you only ever set one sort
param.
/api/v1/cs-pm/users/available-users?sort=id,name
/api/v1/cs-pm/users/available-users?sort=-contact_frequency,name
If for whatever reason you want to grab some data, and only have it return certain fields, for example "Give me all the users the current user can contact, but only give me their ID and name (username)".
You would do this by adding fields
to the query string. Specify multiple fields by separating them with a comma.
/api/v1/cs-pm/users/available-users?fields=id
/api/v1/cs-pm/users/available-users?fields=id,name
All Comstack endpoints will provide paging information of some kind within the paging
root property of the response, next to data
.
The information included will be a previous
and/or next
page if there is one, a total
count of data found (e.g. 99) and the range
of the data returned which is basically number per page.
Here's an example of the standard "Offset pagination" which means normal, pages 1, 2, 3.
previous
and next
properties are returned if there is a next page, though it can happen that these will be present but null
. Any URLs for paging will have the same filters, sorts and range settings applied. Anyway, example
{ "data": [...], "paging": { "range": 10, "total": 14, "current_page": 1, "next": { "title": "Next", "href": "https://..." } } }
On the conversations messages endpoint cursor paging is used. In short this allows us to not rely on the data set remaining consistent, all you need to do is make a request with before
or after
in the query string. By using either you're saying give me all the data before or after the specified Integer ID.
So, what do you get? Well exactly the same as with offset paging in terms of structure, but an extra but of information. In addition to the previous
and next
properties in the response (within paging
) you'll also get cursors
which will have after
and before
. The point of these bits of data are to give you the IDs of the first and last items in the data set, so when a user scrolls to them or near them you can start loading the previous or next page. Note it is possible that a previous
or next
property will be returned and the URL may not return any data, or that they'll be there and null
missing a title
and href
property.
Note that the current_page
property will be omitted when cursor paging is active.
Here are some links which will probably explain it better...
An example:
{ "data": [...], "paging": { "range": 10, "total": 14, "cursors": { "after": 9, "before": 0 } "next": { "title": "Next", "href": "https://..." } } }
List a users conversations.
Requires: Authenticated user session.
Default limit: 10.
Default sort: Updated
DESC
.
Status | Description | Example |
---|---|---|
200 |
Valid response. |
Body{ "data": [ { "type": "conversation", "id": 1, "participants": [ { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" }, { "type": "user", "id": 2, "name": "username2", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av2.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av2.jpg?itok=123" ], "contact_frequency": 0, profile: "http://example.com/users/username2" }, ], "historical_participants": [ { // Same as 'participants' property. } ], "started_by": { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" } "last_updated_by": { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" } "last_message": { "type": "message", "id": 1, "message_type": "cs_pm", "conversation_id": 1, "sender": { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" }, "sent": "", "updated": "", "text": "Quack quack", "weight": 0, "edits": 0, "deleted": false }, "started": "2015-01-15T12:24:47+0000", "updated": "2015-01-15T12:24:47+0000", "container": "", "title": "", "unread_count": 0, "pinned": false, "archived": false, "muted": false, "starred": false, "forwarded": false, "deleted": false }, ... ], "paging": { // See the top of this document for details on pagination. } } |
204 |
No content (no conversations exist for this user). |
Create a new conversation.
Property | Type | Description | Required? |
---|---|---|---|
recipients | Array |
An array of user ids who will be participants in the conversation not including the author. | |
text | String |
The text of the initial message in the conversation. Empty strings are invalid. |
{ "recipients": [1], "text": "Hiiiiiiiiiiiiiiiiiiiiii." }
Status | Description | Example |
---|---|---|
201 |
Content created. |
HeadersLocation: /conversations/{conversation_id} Body{ // See conversation model. } |
400 |
Validation issue or otherwise, see issue text. |
Search all conversations.
Property | Type | Description | Required? |
---|---|---|---|
query | String |
The search query. |
{ "query": "quack" }
Status | Description | Example |
---|---|---|
200 |
Content found. |
{ "data": [ { "type": "conversation_search_result", "conversation": { // See the conversation model. }, "message": { // See the message model. }, "relevance": 1.0, "page": "https://example.com/api/v1/cs-pm/conversations/1/messages?before=124" } ... ], "paging": { // See the top of this document for details on pagination. } } |
204 |
No results were found with that search query. | |
400 |
Validation issue. |
Get a specific conversation.
Requires: Authenticated user session.
Status | Description | Example |
---|---|---|
200 |
Content found. |
Body{ // See conversation model. } |
404 |
Content not found. |
Delete a specific conversation.
Requires: Authenticated user session.
Status | Description | Example |
---|---|---|
200 |
Content deleted. | |
404 |
Content not found. |
Post a reply to a conversation.
Requires: Authenticated user session.
Property | Type | Description | Required? |
---|---|---|---|
text | String |
The text of the reply message to the conversation. Empty strings are invalid. |
{ "text": "This is my reply." }
Status | Description | Example |
---|---|---|
201 |
Content created. |
HeadersLocation: /conversations/{conversation_id}/message/{message_id} Body{ // See message model. } |
400 |
Validation issue, text is missing. | |
404 |
Conversation not found. |
Get messages that belong to a conversation.
Requires: Authenticated user session.
Default limit: 10.
Default sort: sent
DESC
.
Status | Description | Example |
---|---|---|
200 |
An array of messages. |
{ "data": [ { "type": "message", "id": 1, "message_type": "comstack_pm", "conversation_id": 1, "sender": { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" }, "sent": "2015-01-15T12:24:47+0000", "updated": "2015-01-15T12:24:47+0000", "text": "Blah blah...", "weight": 0, "edits": 0, "deleted": false }, ... ], "paging": { // See the top of this document for details on pagination. } } |
204 |
No content (no messages in conversation). | |
404 |
Conversation not found. |
Delete messages that belong to a conversation. This endpoint doesn't conform to REST but there you go. Note that the HTTP method is
POST
.Requires: Authenticated user session.
Property | Type | Description | Required? |
---|---|---|---|
ids | Array |
An array of message ids to be deleted from the conversation. |
{ "ids": [1, 2] }
Status | Description | Example |
---|---|---|
200 |
Messages deleted. | |
400 |
Validation issue, most likely that ids array is missing. |
|
404 |
Content not found. |
Mark a conversation as read, no unread messages within.
Requires: Authenticated user session.
Status | Description | Example |
---|---|---|
200 |
Conversation has been marked as read. | |
404 |
Content not found. |
Mark a conversation as unread.
Requires: Authenticated user session.
Status | Description | Example |
---|---|---|
200 |
Conversation has been marked as unread. | |
404 |
Content not found. |
Remove the authenticated user from the specified conversation, technically remove the user from the
participants
property array.Requires: Authenticated user session.
Status | Description | Example |
---|---|---|
200 |
The user was removed from the conversation. | |
404 |
Conversation not found. |
Invite a user to the conversation.
Requires: Authenticated user session.
Property | Type | Description | Required? |
---|---|---|---|
ids | Array |
An array of user ids to be invited to the conversation. |
{ "ids": [2, 3] }
Status | Description | Example |
---|---|---|
200 |
The user has been invited. | |
400 |
Either the ids were missing or some user ids were invalid. Invalid user ids would be any ids not found in the available users response /api/v1/cs-pm/users/available-users . |
|
404 |
Conversation not found. |
Set the title of a conversation.
Requires: Authenticated user session.
Property | Type | Description | Required? |
---|---|---|---|
text | String |
Empty strings are accepted. |
{ "text": "Doddfest 2015 tickets" }
Status | Description | Example |
---|---|---|
200 |
The title has been set. | |
404 |
Conversation not found. |
Mute notifications that result from this conversation.
Requires: Authenticated user session.
Status | Description |
---|---|
200 |
The conversation has been muted. |
404 |
Conversation not found. |
Unmute this conversation.
Requires: Authenticated user session.
Status | Description |
---|---|
200 |
The conversation has been unmuted. |
404 |
Conversation not found. |
Archive this conversation.
Requires: Authenticated user session.
Status | Description |
---|---|
200 |
The conversation has been archived. |
404 |
Conversation not found. |
Unarchive this conversation.
Requires: Authenticated user session.
Status | Description |
---|---|
200 |
The conversation has been unarchived. |
404 |
Conversation not found. |
Pin this conversation.
Requires: Authenticated user session.
Status | Description |
---|---|
200 |
The conversation has been pinned. |
404 |
Conversation not found. |
Unpin this conversation.
Requires: Authenticated user session.
Status | Description |
---|---|
200 |
The conversation has been unpinned. |
404 |
Conversation not found. |
Star this conversation.
Requires: Authenticated user session.
Status | Description |
---|---|
200 |
The conversation has been starred. |
404 |
Conversation not found. |
Unstar this conversation.
Requires: Authenticated user session.
Status | Description |
---|---|
200 |
The conversation has been unstarred. |
404 |
Conversation not found. |
Search within a conversation.
Property | Type | Description | Required? |
---|---|---|---|
query | String |
The search query. |
{ "query": "tickets" }
Status | Description | Example |
---|---|---|
200 |
Content found. |
{ "data": [ { "type": "message_search_result", "message": { // See the message model. }, "relevance": 1.0, "page": "https://example.com/api/v1/cs-pm/conversations/1/messages?before=124" } ... ], "paging": { // See the top of this document for details on pagination. } } |
204 |
No results were found with that search query. | |
400 |
Validation issue. | |
404 |
Conversation not found. |
Return information about the current user, including the permissions they have. Preferences are also included, only one right now is "read only mode" which is provided by a user preference which allows users to opt out of Private messaging.
Requires: Authenticated user session.
Status | Description | Example |
---|---|---|
200 |
Valid response. |
Body{ "data": { "user": { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" }, "permissions": { "conversations": { "start": true, "leave": true, "reply": true, "invite_others": false, "set_title": false, "mark_as_read": false, "mute": false, "archive": false, "pin": false, "start": false }, "messages": { "edit_own": false, "delete": false } }, "preferences": { "read_only_mode": false, "forced_read_only": false } } } |
401 |
No authorised/logged in. |
List the available users to start conversations with.
Requires: Authenticated user session.
Default limit: none.
Default sort: name
ASC
.
Status | Description | Example |
---|---|---|
200 |
Valid response. |
Body{ "data": [ { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" }, ... ] } |
204 |
No content (no available users). |
Get a message.
Requires: Authenticated user session.
Status | Description | Example |
---|---|---|
200 |
The message has been found. |
{ "data": { // See the message model. } } |
404 |
Message not found. |
Update the message text.
Requires: Authenticated user session.
Property | Type | Description | Required? |
---|---|---|---|
text | String |
The text of the message to set. Empty strings are invalid. |
{ "text": "Quack. Edit to capitalise." }
Status | Description | Example |
---|---|---|
200 |
The conversation has been updated. |
{ "data": { // See the message model. } } |
400 |
Invalid request. | |
404 |
Message not found. |
Property | Type | Description |
---|---|---|
type | String |
The type of entity that this is for convenience, conversation . |
id | Integer |
ID of the current record. |
participants | Array |
An array of user objects that are currently involved in the conversation. |
historical_participants | Array |
An array of user objects that are have been involved in the conversation, this will include anyone who has left the conversation. |
started_by | Object |
A user object of the user who initiated the conversation. |
last_updated_by | Object |
A user object of the user who last posted a reply in the conversation. |
last_message | Object |
A message object, the last/most recently posted message in this conversation. |
started | String |
An ISO 8601 date and time including timezone of when the conversation was started. |
updated | String |
An ISO 8601 date and time including timezone of when the conversation was last updated/replied to. |
container | String |
The machine name of the container that this conversation has been placed in. |
title | String |
The title or name of the conversation if it has one. |
unread_count | Integer |
The number of messages in the conversation that haven't been read by the current user. |
pinned | Boolean |
Whether or not the conversation has been pinned to the top of listings. |
archived | Boolean |
Whether or not the conversation has been archived by the current user. |
muted | Boolean |
Whether or not the conversation has been muted by the current user, if true the user will not receive notifications about new messages in this conversation. |
starred | Boolean |
Whether or not the conversation has been starred by the current user. |
forwarded | Boolean |
Whether or not the conversation has been forwarded to another user by the current user. |
{ "type": "conversation", "id": 1, "participants": [ { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" }, { "type": "user", "id": 2, "name": "username2", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av2.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av2.jpg?itok=123" ], "contact_frequency": 0, profile: "http://example.com/users/username2" }, ], "historical_participants": [ { // Same as 'participants' property. } ], "started_by": { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" }, "last_updated_by": { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" }, "last_message": { "type": "message", "id": 1, "message_type": "cs_pm", "conversation_id": 1, "sender": { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" }, "sent": "", "updated": "", "text": "Quack quack", "weight": 0, "edits": 0, "deleted": false }, "started": "2015-01-15T12:24:47+0000", "updated": "2015-01-15T12:24:47+0000", "container": "", "title": "", "unread_count": 0, "pinned": false, "archived": false, "muted": false, "forwarded": false, "deleted": false }
Property | Type | Description |
---|---|---|
type | String |
The type of entity that this is for convenience, message . |
id | Integer |
ID of the current record. |
message_type | String |
The machine name of the message type, this will more often than not be comstack_pm but could include other types e.g. comstack_pm_event . |
conversation_id | Integer |
The ID of the conversation that this message belongs to. |
sender | Object |
The user object of the user who sent this message. |
sent | String |
An ISO 8601 date and time including timezone of when this message was sent. |
updated | String |
An ISO 8601 date and time including timezone of when this message was updated. |
text | String |
The message text. |
weight | Integer |
Weight of the current message to allow different message types to be ordered in the same list together. Defaults to 0 . |
edits | Integer |
The number of times this message has been edited. |
deleted | Boolean |
Whether or not this conversation has been deleted. Only users with a certain permission will have the API return conversations where this is true. |
{ "type": "message", "id": 1, "message_type": "comstack_pm", "conversation_id": 1, "sender": { "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ] }, "sent": "2015-01-15T12:24:47+0000", "updated": "2015-01-15T12:24:47+0000", "text": "Blah blah...", "weight": 0, "edits": 0, "deleted": false }
Property | Type | Description |
---|---|---|
type | String |
The type of entity that this is for convenience, user . |
id | Integer |
ID of the current record. |
name | String |
This users username. |
avatars | Array |
An array of avatar URLs for this user of varying sizes. |
contact_frequency | Integer |
The number of times this user has been contacted by the current user. |
deleted | Boolean |
Whether or not this message has been deleted. Only users with a certain permission will have the API return messages where this is true. |
{ "type": "user", "id": 1, "name": "username", "avatars": [ "100-100": "https://example.com/sites/default/files/styles/cs-100-100/public/avatars/av1.jpg?itok=123", "200-200": "https://example.com/sites/default/files/styles/cs-200-200/public/avatars/av1.jpg?itok=123" ], "contact_frequency": 0, "profile": "http://example.com/users/username" }
Property | Type | Description |
---|---|---|
type | String |
The type of entity that this is for convenience, conversation_search_result . |
conversation | Object |
The conversation object. |
message | Object |
The message object. |
relevance | Float |
The relevance of this search result used for sorting. |
page | String |
The URL to link to for this result. |
{ "type": "conversation_search_result", "conversation": { // See the conversation model. }, "message": { // See the message model. }, "relevance": 1.0, "page": "https://example.com/api/v1/cs-pm/conversations/1/messages?before=124" }
Property | Type | Description |
---|---|---|
type | String |
The type of entity that this is for convenience, message_search_result . |
message | Object |
The message object. |
relevance | Float |
The relevance of this search result used for sorting. |
page | String |
The URL to link to for this result. |
{ "type": "message_search_result", "message": { // See the message model. }, "relevance": 1.0, "page": "https://example.com/api/v1/cs-pm/conversations/1/messages?before=124" }