This documentation is old. Please visit the new documentation pages at docs.layer.com

Overview

The REST API provides a set of endpoints for querying and performing operations. All data is sent and received as JSON, and all API access is over HTTPS from the following URL:

https://api.layer.com

API Versioning

The API is versioned using a custom media type that encodes the wire format and the version desired. Developers must explicitly request a specific version via the Accept header:

Accept: application/vnd.layer+json; version=1.0

Failure to request a specific version of the API will result in:

Response 406 (Not Acceptable)

{
  "id": "invalid_header",
  "code": 107,
  "message": "Invalid Accept header; must be of form application/vnd.layer+json; version=x.y",
  "url": "https://github.com/layerhq/docs/blob/web-api/specs/rest-api.md#api-versioning",
  "data": {
    "header": "Accept"
  }
}

Retrying Requests

Any request can fail for a variety of reasons; handling should always be in place for retrying a request on failure. Possible failures:

  1. The browser your app is running in has gone offline
  2. The server returns 503 (service unavailable)
  3. The server returns 504 (gateway timeout)

An exponential backoff strategy is recommended for retrying until a request is successful. This strategy should be appropriate for all three cases described above.

While one can build prototypes without implementing some form of retry logic, a production application without retry logic will experience a number of bugs that will cause issues for users.

Rate Limiting

To protect Layer servers from abuse, rate limiting is enforced by the server. Limits are set to be relatively forgiving. Violation of limits will return the Rate Limiting Error 429 (Too Many Requests) which is sent with no response body.

Sample Code

For sample code, visit the Layer for Web Sample Code repo on Github.

Retrieving All Conversations

You can List Conversations using:

GET/conversations/

This will return an array of Conversations ordered by its created_at field in descending order.

curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/conversations

Pagination

All requests that list resources support the pagination API, which includes:

Parameters

Name Type Description
page_size number Number of results to return; default and maximum value of 100.
from_id string Get the Conversations after this ID in the list (before this ID chronologically); can be passed as a layer URI layer:///conversations/uuid or as just a UUID

Headers

All List Resource requests will return a header indicating the total number of results to page through.

Layer-Count: 4023

Pagination example:

curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/conversations?page_size=50&from_id=layer:///conversations/UUID

Response 200 (OK)

[<Conversation>, <Conversation>]

Note, Conversations that you were formerly a participant of will be listed by this request. You will only see messages and metadata up to the point where you stopped being a participant. The participants property will be [] if you are no longer a participant.

Sorting Conversations

The default sort is by Conversation created_at. This is done because this is a fixed ordering, and means that paging can be done without Conversations moving around while paging. This means developers do not need to worry about missed Conversations and changes in ordering of already loaded results.

Many developers however need to see recently active Conversations, so a parameter has been added to this request:

Name Type Description
sort_by string Either created_at to sort by Conversation created date (descending order) or last_message to sort by most recently sent last message (descending order)

Sorting example:

curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/conversations?sort_by=last_message&page_size=50&from_id=layer:///conversations/UUID

Expected results for sorting by last message are as follows:

  1. Results are in descending order; most recently active Conversation comes first
  2. A Conversation that does not have a last message is sorted using its created_at value instead. This means that a Conversation without any messages can still be sorted ahead of a Conversation whose last message is old.

Any developer who sorts using the last_message value is responsible for understanding that results can change while paging. The following recommendations should be followed in using this ordering:

  1. Any Conversation for which a Message Creation Websocket Event is received is now the most recent Conversation:
    1. If the Conversation is already loaded and the app needs a correct order, Move the Conversation to the top of the list.
    2. If the Conversation is not yet loaded, it should be loaded using GET /conversations/UUID and inserted at the top of the list.
  2. Alternatively, if an application needs to always maintain a correctly sorted list, the application can listen for all Conversation Patch websocket event that changes the Conversation's last_message property
    1. If the Conversation isn't yet loaded, load it.
    2. The last_message may have changed due to a new message being sent or the prior Last Message being deleted, so it must be sorted into the list rather than inserted at the top.
  3. Any Conversation Creation Websocket Event will make the new Conversation the most recent Conversation, and thus should be inserted at the top of the list. This is true even if the newly created Conversation does not have any messages.

Retrieving a Conversation

You can get a single Conversation using:

GET/conversations/:conversation_uuid
curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/conversations/CONVERSATION_UUID

Response 200 (OK)

{
  "id": "layer:///conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c",
  "url": "https://api.layer.com/conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c",
  "messages_url": "https://api.layer.com/conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c/messages",
  "created_at": "2015-10-10T22:51:12.010Z",
  "last_message": null,
  "participants": [ "1234", "5678" ],
  "distinct": false,
  "unread_message_count": 0,
  "metadata": {
    "background_color": "#3c3c3c"
  }
}

Creating a Conversation

You can create Conversations using:

POST/conversations

Parameters

Name Type Description
participants string[] Array of User IDs (strings) identifying who will participate in the Conversation
distinct boolean Create or find a Distinct Conversation with these participants
metadata object Arbitrary set of name value pairs representing initial state of Conversation metadata
id string Optional UUID or Layer ID, used for deduplication

Example

{
  "participants": [ "1234", "5678" ],
  "distinct": false,
  "metadata": {
    "background_color": "#3c3c3c"
  }
}
curl  -X POST \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/json" \
      -d '{"participants": ["1234", "5678"], "distinct": false, "metadata": {"background_color": "#3c3c3c"}}' \
      https://api.layer.com/conversations

Response 201 (Created)

{
  "id": "layer:///conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c",
  "url": "https://api.layer.com/conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c",
  "messages_url": "https://api.layer.com/conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c/messages",
  "created_at": "2015-10-10T22:51:12.010Z",
  "last_message": null,
  "participants": [ "1234", "5678" ],
  "distinct": false,
  "unread_message_count": 0,
  "metadata": {
    "background_color": "#3c3c3c"
  }
}

Response 409 (Conflict)

If using deduplication, you may get a conflict if retrying the request:

{
  "id": "id_in_use",
  "code": 111,
  "message": "The requested Conversation already exists",
  "url": "https://developer.layer.com/docs/client/websockets#create-requests",
  "data": {
    "id": "layer:///conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c",
    "url": "https://api.layer.com/conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c",
    "messages_url": "https://api.layer.com/conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c/messages",
    "created_at": "2015-10-10T22:51:12.010Z",
    "last_message": null,
    "participants": [ "1234", "5678" ],
    "distinct": false,
    "unread_message_count": 0,
    "metadata": {
      "background_color": "#3c3c3c"
    }
  }
}

Distinct Conversations

Distinct Conversations are defined in The Conversation Object. When creating a Distinct Conversation, there are three possible results:

Response 201 (Created)

If there is no existing Distinct Conversation that matches the request, then a new Conversation is created and returned.

{
  "id": "layer:///conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c",
  "url": "https://api.layer.com/conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c",
  "messages_url": "https://api.layer.com/conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c/messages",
  "created_at": "2015-10-10T22:51:12.010Z",
  "last_message": null,
  "participants": [ "1234", "5678" ],
  "distinct": false,
  "unread_message_count": 0,
  "metadata": {
    "background_color": "#3c3c3c"
  }
}

Response 200 (OK)

If there is a matching Distinct Conversation, and one of these holds true, then an existing Conversation is returned.

  1. The metadata property was not included in the request
  2. The metadata property was included but with a value of null
  3. The metadata property value is identical to the metadata of the matching Distinct Conversation
{
  "id": "layer:///conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c",
  "url": "https://api.layer.com/conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c",
  "messages_url": "https://api.layer.com/conversations/74a676c4-f697-45c4-b3bc-3e48bd2e372c/messages",
  "created_at": "2015-10-10T22:51:12.010Z",
  "last_message": null,
  "participants": [ "1234", "5678" ],
  "distinct": false,
  "unread_message_count": 0,
  "metadata": {
    "background_color": "#3c3c3c"
  }
}

Response 409 (Conflict)

If the matching Distinct Conversation has metadata different from what was requested, return an error that contains the matching Conversation so that the application can determine what steps to take next (e.g. use the Conversation or modify it using PATCH requests).

{
  "id": "conflict",
  "code": 108,
  "message": "The requested Distinct Conversation was found but had metadata that did not match your request.",
  "url": "https://developer.layer.com/api.md#creating-a-conversation",
  "data": {
    "id": "layer:///conversations/f3cc7b32-3c92-11e4-baad-164230d1df67",
    "url": "https://api.layer.com/conversations/f3cc7b32-3c92-11e4-baad-164230d1df67",
    "created_at": "2014-09-15T04:44:47+00:00",
    "participants": [ "1234", "5678" ],
    "distinct": true,
    "metadata": {
      "background_color": "#3c3c3c"
    }
  }
}

Timing of Conversation Creation Calls

There is a timing issue that some developers will encounter using this operation. Everything here applies to both Messages and Conversations; Messages are used in the example below. The desired behavior:

  1. Create a local representation of the Message
  2. Fire an xhr call to the server to create the Message on the server
  3. Get the response and assign the id provided by the server to your Message.
  4. Get a WebSocket event notifying you that a new Message was created; your code detects that you already know about the object because its ID matches the one you just created; you ignore the event.

The actual timing ends up being something like this:

  1. Create a local representation of the Message
  2. Fire an xhr call to the server to create the Message on the server
  3. Get a websocket event notifying you that a new Message was created; there is no information to clearly associate this information with the xhr request you just made, so you add a new Message to your Conversation. You now have two copies of your Message, one with an ID, the other waiting for the ID. Both of them are presumably rendered in your View.
  4. Get the response and assign the id provided by the server to your object; you now need to delete one of your two Messages.

To avoid this issue, developers can create Messages and Conversations using The Websocket API which supports the desired behavior.

Deleting a Conversation

Conversations will sometimes need to be deleted, and this can be done using a REST delete method called on the URL for the resource to be deleted.

Parameters

Name Type Description
mode string "all_participants" or "my_devices" selects whether to delete it for everyone, or just to remove the Conversation from this user's account.
leave boolean If true, and if mode="my_devices", the user is leaving the Conversation, and is no longer a participant. default is false.

There are three permutations allowed:

  • mode=all_participants: The Conversation is entirely deleted for all users.
  • mode=my_devices&leave=false: The Conversation is deleted for this user only, but this user is still a participant. New Messages will cause the Conversation to be recreated, with Message history starting at with the Message after Deletion occurred.
  • mode=my_devices&leave=true: The Conversation is deleted for this user only, AND this user is removed as a participant. Further Messages will not affect this user, and all users will see the user is no longer in the participant list.

You can delete a Conversation using:

DELETE/conversations/:conversation_uuid?mode=all_participants
curl  -X DELETE \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/json" \
      https://api.layer.com/conversations/CONVERSATION_UUID?mode=all_participants

Response 204 (No Content)

The standard successful response is a 204 (No Content). Note that it may take time to propagate to all devices, during which time other users/devices may attempt to send messages on this Conversation.

Response 404 (Not Found)

{
  "id": "not_found",
  "code": 102,
  "message": "A Conversation with the specified identifier could not be found.",
  "url": "https://developer.layer.com/api.md#conversations"
}

The specified Conversation may have already been deleted or never existed.

Response 403 (Forbidden)

{
  "id": "access_denied",
  "code": 101,
  "message": "You are no longer a participant in the specified Conversation.",
  "url": "https://developer.layer.com/api.md#conversations"
}

The user is not currently a participant with permission to delete the Conversation.

Note that if the user was never a participant in the Conversation, then the user will instead get the not_found error.

Layer-Patch

Messages and Conversations typically respond to a PATCH request to modify the object. There are only a few properties currently exposed to be modified via a PATCH request:

  • Conversation.participants
  • Conversation.metadata

Patch requests are performed using the Layer-Patch format. Typical requests consist of an array of operations, containing the following properties:

Name Type Description
operation string The type of operation to perform (add, remove, set or delete).
property string The property to change; "." separated if its an embedded property.
value string or object Value to add, remove or set.

All Layer-Patch requests require a Content-Type in the header of:

Content-Type: application/vnd.layer-patch+json

Adding/Removing Participants

You can add and remove participants in a Conversation.

Example Add Participants

[
  {"operation": "add", "property": "participants", "value": "user1"},
  {"operation": "add", "property": "participants", "value": "user2"}
]
curl  -X PATCH \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/vnd.layer-patch+json" \
      -d '[{"operation": "add", "property": "participants", "value": "user1"}, {"operation": "add", "property": "participants", "value": "user2"}]' \
      https://api.layer.com/conversations/CONVERSATION_UUID

Response 204 (No Content)

The standard response for any PATCH operation is 204 (No Content).

Example Remove Participants

[
  {"operation": "remove", "property": "participants", "value": "user1"},
  {"operation": "remove", "property": "participants", "value": "user2"}
]
curl  -X PATCH \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/vnd.layer-patch+json" \
      -d '[{"operation": "remove", "property": "participants", "value": "user1"}, {"operation": "remove", "property": "participants", "value": "user2"}]' \
      https://api.layer.com/conversations/CONVERSATION_UUID

Example of Replacing Participants

[
  {"operation": "set", "property": "participants", "value": ["user1", "user2", "user3"]}
]

This will replace the entire set of participants with a new list. Be warned however that if other users are actively adding/removing participants, doing a set operation will destroy any changes they are making.

curl  -X PATCH \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/vnd.layer-patch+json" \
      -d '[{"operation": "remove", "property": "participants", "value": ["user1", "user2", "user3"]}]' \
      https://api.layer.com/conversations/CONVERSATION_UUID

Updating Conversation Metadata

You can set and delete metadata keys in a Conversation.

Example Set a Metadata Property

[
  { "operation": "set", "property": "metadata.a.b.count", "value": "42" },
  { "operation": "set", "property": "metadata.a.b.word_of_the_day", "value": "Argh" }
]

This operation will create or set the count and word_of_the_day properties of b to "42" and "Argh", creating any structures needed to accomplish that (creating the object a and b if they don't exist):

{
  "metadata": {
    "a": {
      "b": {
        "count": "42",
        "word_of_the_day": "Argh"
      }
    }
  }
}
curl  -X PATCH \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/vnd.layer-patch+json" \
      -d '[{ "operation": "set", "property": "metadata.a.b.count", "value": "42" }, { "operation": "set", "property": "metadata.a.b.word_of_the_day", "value": "Argh" }]' \
      https://api.layer.com/conversations/CONVERSATION_UUID

Recall that only string values are allowed in metadata, so 42 must be passed as a string rather than a number. For more information on metadata see the Concepts Section.

Example of Set All Metadata

Note that the entire metadata structure can be replaced using:

[
  {
    "operation": "set",
    "property": "metadata",
    "value": {
        "a": "b",
        "c": {
          "d": "e"
        }
      }
   }
]

Note that this behavior could result in conflicts if other users are changing metadata, so is best avoided.

Retrieving All Messages

You can List Messages using:

GET/conversations/:conversation_uuid/messages

Get the most recent Messages in a Conversation:

curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/conversations/CONVERSATION_UUID/messages

The Layer-Conversation-Is-Syncing header

If a user has just been added to a Conversation that already contains many Messages, and the application immediately requests messages for that Conversation, it is likely that no Messages or an incomplete set of Messages will be loaded from the server. The server needs to copy in the Messages of the newly added Conversation into that user's account, and as long as that server process is running, the Layer-Conversation-Is-Syncing: true header will be returned in the results to indicate that more results are arriving. If you did not receive enough results, check back in a few seconds.

When sync has been completed:

  • The LayerCount header will be updated to show the actual number of Messages in the Conversation (until then, this header value may be misleading)
  • The GET /messages request will not return a complete list, and may return an empty list
  • The Layer-Conversation-Is-Syncing header returned by the server on all GET /messages requests for this Conversation will be true.

Pagination

All requests that list resources support the pagination API, which includes:

Parameters

Name Type Description
page_size number Number of results to return; default and maximum value of 100.
from_id string Get the Messages after this ID in the list (before this ID chronologically); can be passed as a layer URI layer:///messages/uuid or as just a UUID

Headers

All List Resource requests will return a header indicating the total number of results to page through.

Layer-Count: 4023

Pagination example:

curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/conversations/CONVERSATION_UUID/messages?from_id=UUID

Response 200 (OK)

[<Message>, <Message>]

Retrieving a Message

You can get a single Message using:

GET/messages/:message_uuid
curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/messages/MESSAGE_UUID

Response 200 (OK)

{
  "id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67",
  "url": "https://api.layer.com/messages/940de862-3c96-11e4-baad-164230d1df67",
  "receipts_url": "https://api.layer.com/messages/940de862-3c96-11e4-baad-164230d1df67/receipts",
  "conversation": {
    "id": "layer:///conversations/f3cc7b32-3c92-11e4-baad-164230d1df67",
    "url": "https://api.layer.com/conversations/f3cc7b32-3c92-11e4-baad-164230d1df67"
  },
  "parts": [
    {
      "body": "Hello, World!",
      "mime_type": "text/plain"
    },
    {
      "body": "YW55IGNhcm5hbCBwbGVhc3VyZQ==",
      "mime_type": "image/jpeg",
      "encoding": "base64"
    }
  ],
  "sent_at": "2014-09-09T04:44:47+00:00",
  "sender": {
    "name": null,
    "user_id": "5678"
  },
  "recipient_status": {
    "5678": "read",
    "1234": "sent"
  }
}

Sending a Message

You can create and send a Message using:

POST/conversations/:conversation_uuid/messages

Parameters

Name Type Description
parts MessagePart[] Array of MessageParts
parts.body string Text or base64 encoded data for your message, up to 2KB in size
parts.mime_type string text/plain, image/png or other mime type describing the body of this MessagePart
parts.encoding string If sending base64 encoded data, specify base64 else omit this field
parts.content Content If sending Rich Content, use the Content object
notification object See the Push Notifications Section for detailed options
id string Optional UUID or Layer ID, used for deduplication

Example

{
  "parts": [
    {
      "body": "Hello, World!",
      "mime_type": "text/plain"
    },
    {
      "body": "YW55IGNhcm5hbCBwbGVhc3VyZQ==",
      "mime_type": "image/jpeg",
      "encoding": "base64"
    }
  ],
  "notification": {
    "title": "Alert",
    "text": "This is the alert text to include with the Push Notification.",
    "sound": "chime.aiff"
  }
}
curl  -X POST \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/json" \
      -d '{"parts": [ {"body": "Hello world", "mime_type": "text/plain"}, {"body": "YW55IGNhcm5hbCBwbGVhc3VyZQ==", "mime_type": "image/jpeg", "encoding": "base64"}  ],  "notification": { "text": "This is the alert text", "sound": "chime.aiff" }}' \
      https://api.layer.com/conversations/CONVERSATION_UUID/messages

Response 201 (Created)

{
  "id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67",
  "url": "https://api.layer.com/messages/940de862-3c96-11e4-baad-164230d1df67",
  "receipts_url": "https://api.layer.com/messages/940de862-3c96-11e4-baad-164230d1df67/receipts",
  "conversation": {
    "id": "layer:///conversations/f3cc7b32-3c92-11e4-baad-164230d1df67",
    "url": "https://api.layer.com/conversations/f3cc7b32-3c92-11e4-baad-164230d1df67"
  },
  "parts": [
    {
      "body": "Hello, World!",
      "mime_type": "text/plain"
    },
    {
      "body": "YW55IGNhcm5hbCBwbGVhc3VyZQ==",
      "mime_type": "image/jpeg",
      "encoding": "base64"
    }
  ],
  "sent_at": "2014-09-09T04:44:47+00:00",
  "sender": {
    "name": null,
    "user_id": "5678"
  },
  "recipient_status": {
    "5678": "read",
    "1234": "sent"
  }
}

Response 409 (Conflict)

If using deduplication, you may get a conflict if retrying the request:

{
  "id": "id_in_use",
  "code": 111,
  "message": "The requested Message already exists",
  "url": "https://developer.layer.com/docs/client/websockets#create-requests",
  "data": {
    "id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67",
    "url": "https://api.layer.com/messages/940de862-3c96-11e4-baad-164230d1df67",
    "receipts_url": "https://api.layer.com/messages/940de862-3c96-11e4-baad-164230d1df67/receipts",
    "conversation": {
      "id": "layer:///conversations/f3cc7b32-3c92-11e4-baad-164230d1df67",
      "url": "https://api.layer.com/conversations/f3cc7b32-3c92-11e4-baad-164230d1df67"
    },
    "parts": [
      {
        "body": "Hello, World!",
        "mime_type": "text/plain"
      },
      {
        "body": "YW55IGNhcm5hbCBwbGVhc3VyZQ==",
        "mime_type": "image/jpeg",
        "encoding": "base64"
      }
    ],
    "sent_at": "2014-09-09T04:44:47+00:00",
    "sender": {
      "name": null,
      "user_id": "5678"
    },
    "recipient_status": {
      "5678": "read",
      "1234": "sent"
    }
  }
}

Special Rules

  1. Message parts whose bodies cannot be encoded as a JSON string need to be encoded as Base64, and the message part's encoding property should be "base64".
  2. The un-encoded length of a message part cannot exceed 2KB. The server will decode such message parts before transmitting them to clients that can accept binary data.

Push Notifications

Layer provides extensive support for Push Notifications on both iOS (APNS) and Android (GCM). Pushes are delivered to devices when Messages are sent using the notification parameter. The possible values for the notification object are described at: Push Notifications.

Note that values for iOS badge counts cannot be provided because the pushes are fanned out to all participants.

Push Notifications are an optional feature and the notification parameter can be omitted entirely for Web-to-Web communication use-cases.

Timing of Create Message Calls

See Timing of Conversation Creation Calls for details on why you might prefer to use the WebSocket API to send messages.

Deleting a Message

Messages will sometimes need to be deleted, and this can be done using a REST delete method called on the URL for the resource to be deleted.

Parameters

Name Type Description
mode string "all_participants" or "my_devices" selects whether to delete it for everyone, or just to remove the Message from this user's account.

Note that there is no undelete, regardless of whether the Message is deleted for all users or just for this user.

You can delete a Message using:

DELETE/messages/:message_uuid?mode=all_participants
curl  -X DELETE \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/json" \
      https://api.layer.com/messages/MESSAGE_UUID?mode=all_participants

Response 204 (No Content)

The standard successful response is a 204 (No Content).

Retrieving All Announcements

You can List Announcements using:

GET/announcements

Get the most recent Announcements:

curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/announcements

Pagination

All requests that list resources support the pagination API, which includes:

Parameters

Name Type Description
page_size number Number of results to return; default and maximum value of 100.
from_id string Get the Announcements after this ID in the list (before this ID chronologically); can be passed as a layer URI layer:///announcements/uuid or as just a UUID

Headers

All List Resource requests will return a header indicating the total number of results to page through.

Layer-Count: 4023

Pagination example:

curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/announcements?from_id=UUID

Response 200 (OK)

[<Announcement>, <Announcement>]

Retrieving an Announcement

You can get a single Announcement using:

GET/announcements/:announcement_uuid
curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/announcements/ANNOUNCEMENT_UUID

Response 200 (OK)

{
  "id": "layer:///announcements/940de862-3c96-11e4-baad-164230d1df67",
  "url": "https://api.layer.com/announcements/940de862-3c96-11e4-baad-164230d1df67",
  "receipts_url": "https://api.layer.com/announcements/940de862-3c96-11e4-baad-164230d1df67/receipts",
  "parts": [
    {
      "body": "Hello, World!",
      "mime_type": "text/plain"
    }
  ],
  "sent_at": "2014-09-09T04:44:47+00:00",
  "sender": {
    "name": "The World"
  },
  "recipient_status": {
    "5678": "read"
  }
}

Deleting an Announcement

You can delete an Announcement using:

DELETE/announcements/:announcement_uuid
curl  -X DELETE \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/json" \
      https://api.layer.com/announcements/ANNOUNCEMENT_UUID

Response 204 (No Content)

The standard successful response is a 204 (No Content).

Push Tokens

When the Client API is used from mobile frameworks (React Native, Cordova, Titanium, etc.), users can register and unregister the native push tokens for APNs and GCM to allow OS notifications for the app, just like with a native Layer mobile app.

Before registering push tokens for your clients, you need to configure your APNS and/or GCE credentials in the Developer Dashboard. Select the Push link in the Developer Dashboard and follow the instructions to enable notifications for Android and/or IO.S

Testing Push Configuration

After configuring your push credentials and registering a push token for your mobile client, you'll need to send a message from another authenticated user that includes push configuration as you will not receive push notifications for your own content.

Registering a Push Token

You can register your device to start receiving push notifications from Layer using:

POST/push_tokens

Parameters

Name Type Description
token string The APNs or GCM push token to register
type string Either apns or gcm
device_id string A stable id that identified the device, from the OS. This is only used to avoid duplicate tokens on a device
apns_bundle_id string (optional) The APNS bundle id corresponding to the token
gcm_sender_id string (optional) The GCM sender id corresponding to the token
ios_version string (optional) Should be a version number like 9.0. This determines how pushes are sent to iOS device, which changed in iOS 8. The new behavior, which allows larger payloads, is the default

Example

{
    "token": "105ebe3fcb7e93efda22257caaf5b9c465043f6d0b2abf3bc8ae7c939655e949",
    "type": "apns",
    "device_id": "a7775566-bfbf-11e5-bf72-359a01002888",
    "apns_bundle_id": "com.layer.bundleid",
    "ios_version": "9.0"
}
curl  -X POST \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/json" \
      -d '{"token": "105ebe3fcb7e93efda22257caaf5b9c465043f6d0b2abf3bc8ae7c939655e949", "type": "apns", "device_id": "a7775566-bfbf-11e5-bf72-359a01002888", "apns_bundle_id": "com.layer.bundleid"}' \
      https://api.layer.com/push_tokens

Response 202 (Accepted)

Empty Body

Each user may have up to 25 push tokens. If you attempt to add more than 25, the least recently used (LRU) push token will be unregistered in order to accommodate the one being added. We reserve the right to change this limit in the future.

Unregister a Push Token

You can delete a push token using:

DELETE/push_tokens/:device_id

Example

curl  -X DELETE \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/push_tokens/a7775566-bfbf-11e5-bf72-359a01002888

Response 202 (Accepted)

Empty Body

Read and Delivery Receipts

Delivery and Read Receipts are used for three purposes:

  1. Notify other users that this user has received or has read the Message.
  2. Allows the UI to emphasize Messages that have not been read by the current user.
  3. Allows Conversations to track how many unread messages they contain for the current user, for use in UIs for badging or other unread count indicators.

The Message's read state is stored in two Message properties:

  1. is_unread: This will be changed from true to false once a read receipt has been posted. This value only indicates whether the user associated with this session has read the message.
  2. recipient_status: This is a JSON hash of participants with a value of either "sent", "read" or "delivered" for each participant.

Additionally, the Conversation.unread_message_count should decrement after changing is_unread using a Read Receipt.

Note, that a Message that has been marked as read cannot be marked as unread. A delivery receipt on a Message that has already been marked as read will have no effect.

You can send receipts using:

POST/messages/:message_uuid/receipts
POST/announcements/:announcement_uuid/receipts

Parameters

Name Type Description
type string "read" or "delivery"

Example

{
  "type": "read"
}
curl  -X POST \
      -H 'Accept: application/vnd.layer+json; version=1.0' \
      -H 'Authorization: Layer session-token='TOKEN'' \
      -H 'Content-Type: application/json' \
      -d '{"type": "read"}' \
      https://api.layer.com/messages/MESSAGE_UUID/receipts
curl  -X POST \
      -H 'Accept: application/vnd.layer+json; version=1.0' \
      -H 'Authorization: Layer session-token='TOKEN'' \
      -H 'Content-Type: application/json' \
      -d '{"type": "read"}' \
      https://api.layer.com/announcements/ANNOUNCEMENT_UUID/receipts

Response 204 (No Content)

The standard response to this request is simply 204 (No Content).

Rich Content

The Rich Content feature exists to allow messages whose body is larger than 2KB to be sent. Rich Content works by uploading your content to a cloud file store, and attaching the URL and information about that file store to the Message. Rich Content uses the Content Object defined in the Rich Content Introduction.

The process for sending Rich Content is:

  1. POST /content and get an upload_url and Content ID
  2. Upload your content to the specified URL
  3. Create a Message with the Content ID

Initiating a Rich Content Upload

You can create a new Content resource using:

POST/content

Parameters

The request includes the following additional headers:

Name Description Example
Upload-Content-Type Mime type for the content to be uploaded "image/png"
Upload-Content-Length Size of the content to be uploaded 10001
Upload-Origin Browsers need CORS headers, so browsers should provide the Origin for the request window.location.origin

The response will either be a 201 (Created) response that includes a content object or an error indicating why the operation failed.

Example

curl  -X POST \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      -H "Content-Type: application/json" \
      -H "Upload-Content-Type: image/jpeg" \
      -H "Upload-Content-Length: 10000" \
      -H "Upload-Origin: http://mydomain.com" \
      https://api.layer.com/content

Response 201 (Created)

Standard response from creating a Content; the response will include a Location header:

Location: /content/3d0736d9-1a50-4e9a-a9b3-2400caa9e161

And a Content Object with your Content ID and upload_url.

{
  "id": "layer:///content/3d0736d9-1a50-4e9a-a9b3-2400caa9e161",
  "download_url": null,
  "expiration": null,
  "upload_url": "https://www.googleapis.com/upload/storage/v1/b/myBucket/o?uploadType=resumable&upload_id=xa298sd_sdlkj2"
}

The URL specified via the upload_url field in the body will be a Google Cloud Storage resumable upload URL. The specifics for completing the upload are detailed in the Google Cloud Storage JSON API Docs.

Sending a Message including Rich Content

Once the Rich Content upload has completed, the client can send a Message that includes the Rich Content part using:

POST/conversations/:conversation_uuid/messages

Parameters

The Content Object requires the following parameters when sending:

Name Type Description
id string ID for the content that was returned from POST /content. (e.g. layer:///content/cbf5f150-2f45-11e5-82f7-0242ac1100e3)
size number Size of the file that was uploaded; should match the Upload-Content-Length header.

Example Sending Rich Content Message

{
  "parts": [
    {
      "mime_type": "text/plain",
      "body": "This is the message."
    },
    {
      "mime_type": "image/png",
      "content": {
        "id": "layer:///content/7a0aefb8-3c97-11e4-baad-164230d1df67",
        "size": 10001
      }
    }
  ]
}

Response 201 (Created)

Standard response from creating a Message.

Downloading a Rich Content Message Part

Messages that include Rich Content Message Parts will include an authenticated, expiring URL for downloading the content embedded in the Message:

{
  "parts": [
    {
      "id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/0",
      "mime_type": "text/plain",
      "body": "This is the message."
    },
    {
      "id": "layer:///messages/940de862-3c96-11e4-baad-164230d1df67/parts/1",
      "mime_type": "image/png",
      "content": {
        "id": "layer:///content/8c839735-5f95-439a-a867-30903c0133f2",
        "download_url": "http://google-testbucket.storage.googleapis.com/testdata.txt?GoogleAccessId=1234567890123@developer.gserviceaccount.com&Expires=1331155464&Signature=BClz9e4UA2MRRDX62TPd8sNpUCxVsqUDG3YGPWvPcwN%2BmWBPqwgUYcOSszCPlgWREeF7oPGowkeKk7J4WApzkzxERdOQmAdrvshKSzUHg8Jqp1lw9tbiJfE2ExdOOIoJVmGLoDeAGnfzCd4fTsWcLbal9sFpqXsQI8IQi1493mw%3D",
        "expiration": "2014-09-09T04:44:47+00:00",
        "refresh_url": "https://api.layer.com/content/8c839735-5f95-439a-a867-30903c0133f2",
        "size": 172114124
      }
    }
  ]
}

Using the download_url should allow you to request and render the content. Some caveats to be aware of:

  1. Photos displayed in web browsers will not use exif data to rotate the image to the correct orientation. Images need to be rotated before being uploaded, or JavaScript libraries such as blueimp-load-image should be used to parse exif data and rotate the images.
  2. If loading content via XHR request rather than than directly setting a src property on a dom node, you will need to insure that CORS requests are enabled. This is addressed by including the Upload-Origin header in POST /content which tells the cloud storage service to allow CORS requests from that origin.

Refreshing the download URL for a Content Object

The download_url will expire periodically; the expiration field will tell you when. Every time you fetch a message from the server it will come with a new, fresh download URL and expiration. The download URL for a particular content object can be refreshed using:

GET/content/:content_id
curl  -X GET \
      -H "Accept: application/vnd.layer+json; version=1.0" \
      -H "Authorization: Layer session-token='TOKEN'" \
      https://api.layer.com/content/8a896e15-2908-4d7c-9f4e-d47e18ae2774

The response will be a JSON document that includes a newly refreshed URL for accessing the rich content:

Response 200 (OK)

{
  "id": "8a896e15-2908-4d7c-9f4e-d47e18ae2774",
  "download_url": "http://google-testbucket.storage.googleapis.com/some/download/path",
  "expiration": "2014-09-09T04:44:47+00:00",
  "upload_url": null
}

Note, The GET request can only be issued if content has been uploaded to cloud storage. If a Content resource was created on the REST server but the data never uploaded to storage, this request fails.

Error Handling

Clients must be prepared to handle a variety of errors that may be returned by the server. Errors responses will have an appropriate HTTP status code and the response body will contain a JSON representation of the error:

Response 422 (Unprocessable Entity)

{
  "id": "missing_property",
  "code": 104,
  "message": "The participants list cannot be omitted.",
  "url": "https://developer.layer.com/api.md#creating-a-conversation",
  "data": {
    "property": "participants"
  }
}

Error Attributes

Name Type Description Example
id string Unique string error identifier access_denied
code integer Unique numeric error code 12345
message string Details of the error The participants list cannot be omitted
url string A URL to a reference with more info about the error https://developer.layer.com/client/introduction#authentication
data dictionary A free form dictionary of supplemental data specific to the error { "nonce": "38ca1bb2-2560-44d4-88bb-5989ce9b2b66" }

Full List

code id Context HTTP Status Description
1 service_unavailable Client 503 (Service Unavailable) The operation could not be completed because a backend service could not be accessed
2 invalid_app_id Client 403 (Forbidden) The client provided an invalid Layer App ID
3 invalid_request_id Client 400 (Bad Request) The client has supplied a request ID that is not a valid UUID
4 authentication_required Client 401 (Unauthorized) The action could not be completed because the client is unauthenticated The response will include a nonce for satisfying an authentication challenge
7 rate_limit_exceeded Client 429 (Too Many Requests) The client has sent too many requests in a given amount of time
8 request_timeout Client 408 (Request Timeout) or None The server or the client timed out waiting for a request to complete
9 invalid_operation Client 422 (Unprocessable Entity) or None The server or client has declined to perform an invalid operation (i.e. deleting an unsent message)
10 invalid_request Client 400 (Bad Request) The request is structurally invalid
100 internal_server_error Client 500 (Internal Server Error) The operation could not be completed because an unexpected error occurred
101 access_denied Resource 403 (Forbidden) The authenticated user does not have access to the resource requested
102 not_found Resource 404 (Not Found) The resource requested could not be found
104 missing_property Resource 422 (Unprocessable Entity) A property with a required value was not supplied
105 invalid_property Resource 422 (Unprocessable Entity) A property was supplied with an invalid value
106 invalid_endpoint Client 404 (Not Found) The endpoint 'GET /nonce' does not exist
107 invalid_header Client 406 (Not Acceptable) Invalid Accept header; must be of form application/vnd.layer+json; version=x.y
108 conflict Resource 409 (Conflict) The distinct conversation already exists with conflicting metadata
109 method_not_allowed Resource 405 (Method Not Allowed) The HTTP method used is not allowed for the given resource
110 participant_blocked Resource 422 (Unprocessable Entity) The conversation could not be created because at least one participant is blocked.
111 id_in_use Resource 409 (Conflict) The id specified for this conversation is not unique.

invalid_app_id

The Application ID provided to a POST /sesssion call was invalid. This service accepts IDs in the form of layer://apps/production/uuid, layer://apps/staging/uuid and just uuid. See Obtain a Session Token.

invalid_request_id

The client has supplied a request ID that is not a valid UUID. You can try and validate your UUID with a public UUID validator such as this one.

access_denied

You have attempted to access a resource to which your user does not have permissions. Typically this means that the user was a participant of this Conversation, but is no longer a participant. So it still shows up in their data, but they no longer have permissions to operate upon it.

not_found

You have attempted to access a resource that was not found. Typical causes include:

  1. The resource was deleted
  2. The resource ID is incorrect
  3. The user has never been a participant who can access this data; from the user's perspective, this data does not exist.

invalid_header

This error typically occurs when:

  1. A request is made without an appropriate Accept header; see API Versioning for header requirements.
  2. A request was made with an appropriate Accept header, but requested a version that is not available.
  3. A PATCH operation is performed with an incorrect Content-Type header; see Layer-Patch for header requirements.

participant_blocked

If user Alice has blocked user Bob, in addition to silently hiding messages from Bob to Alice, Layer prevents Alice from creating new conversations with Bob or adding Bob to conversations, which indicates some confusion. In order to successfully create the conversation, have Alice unblock Bob. | 111 | id_in_use | Resource | 409 (Conflict) | The id specified for this conversation is not unique. |

id_in_use

If you specify a random uuid when creating a conversation or message, it will become the id for that object. This error means that the id you've chosen is already in use. Most likely this means that you tried to create a conversation and succeeded but never got acknowledgement from the server, and now you're retrying with the same ID as part of the Deduplication process.

The conversation or message will be included in the error body for you to compare, and if they are in fact the same you can proceed. In the unlikely case that your randomly chosen uuid conflicted with some unrelated conversation you can just choose a new id and retry.

If you see this error frequently outside of a deduplication process, you should verify that you are generating unique uuids before each create request.