Using the REST API

LiveObjects provides a comprehensive REST API that allows you to directly work with objects without using a client SDK.

View the REST API authentication documentation for details on how to authenticate your requests.

To use LiveObjects, an API key must have at least the object-subscribe capability. With only this capability, clients will have read-only access, preventing them from publishing operations.

In order to create or update objects, make sure your API key includes both object-subscribe and object-publish capabilities to allow full read and write access.

When working with objects via the REST API, primitive types and object references are included in request and response bodies under data fields.

The key in the data object indicates the type of the value:

{ "data": { "number" : 42 }} { "data": { "string" : "LiveObjects is awesome" }} { "data": { "boolean" : true }} { "data": { "bytes": "TGl2ZU9iamVjdHMgaXMgYXdlc29tZQo=" }} { "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" }}
Copied!

GET rest.ably.io/channels/<channelId>/objects

Fetch a flat list of objects on the channel:

curl -X GET "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!

The response includes the IDs of the objects on the channel:

[ "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519", "root", ]
Copied!

To include values of the objects in the response, set the values=true query parameter:

curl -X GET "https://rest.ably.io/channels/my-channel/objects?values=true" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!
[ { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669", "counter": { "data": { "number": 10 } } }, { "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "counter": { "data": { "number": 5 } } }, { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519", "map": { "entries": { "down": { "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" } }, "up": { "data": { "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269" } } } } }, { "objectId": "root", "map": { "entries": { "votes": { "data": { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519" } } } } } ]
Copied!

You can optionally include additional object metadata with the metadata query option:

curl -X GET "https://rest.ably.io/channels/my-channel/objects?values=true&metadata=true" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!
[ { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669", "counter": { "data": { "number": 10 } }, "siteTimeserials": { "e02": "01745828651671-000@e025VxXLABoR0C19591332:000" }, "tombstone": false }, { "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "counter": { "data": { "number": 5 } }, "siteTimeserials": { "e02": "01745828645271-000@e025VxXLABoR0C19591332:000" }, "tombstone": false }, { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519", "map": { "mapSemantics": "LWW", "entries": { "down": { "timeserial": "01745828651671-000@e025VxXLABoR0C19591332:001", "tombstone": false, "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" } }, "up": { "timeserial": "01745828645271-000@e025VxXLABoR0C19591332:001", "tombstone": false, "data": { "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269" } } } }, "siteTimeserials": { "e02": "01745828651671-000@e025VxXLABoR0C19591332:001" }, "tombstone": false }, { "objectId": "root", "map": { "mapSemantics": "LWW", "entries": { "votes": { "timeserial": "01745828596522-000@e025VxXLABoR0C19591332:001", "tombstone": false, "data": { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519" } } } }, "siteTimeserials": { "e02": "01745828596522-000@e025VxXLABoR0C19591332:001" }, "tombstone": false } ]
Copied!

The response can be paginated with cursor and limit query params using relative links.

Use the limit query parameter to specify the maximum number of objects to include in the response:

curl -v -X GET "https://rest.ably.io/channels/my-channel/objects?values=true&limit=2" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!
[ { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669", "counter": { "data": { "number": 10 } } }, { "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "counter": { "data": { "number": 5 } } } ]
Copied!

The response includes Link headers which provide relative links to the first, current and next pages of the response:

link: </channels/my-channel/objects?limit=2&values=true>; rel="first" link: </channels/my-channel/objects?limit=2&values=true>; rel="current" link: </channels/my-channel/objects?cursor=map%3Aja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek%401745828596519&limit=2&values=true>; rel="next"
Copied!

The list objects endpoints returns objects ordered lexicographically by object ID. The object ID of the first object in the next page is used as the cursor value for the next request:

curl -X GET "https://rest.ably.io/channels/my-channel/objects?cursor=map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519&limit=2&values=true" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!
[ { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519", "map": { "entries": { "down": { "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" } }, "up": { "data": { "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269" } } } } }, { "objectId": "root", "map": { "entries": { "votes": { "data": { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519" } } } } } ]
Copied!

GET rest.ably.io/channels/<channelId>/objects/<objectId>

To fetch a single object on the channel, specify the object ID in the URL path:

curl -X GET "https://rest.ably.io/channels/my-channel/objects/root" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!

The response contains a single object referencing any nested child objects by their object ID:

{ "objectId": "root", "map": { "entries": { "votes": { "data": { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519" } } } } }
Copied!

To fetch the objects on the channel in a tree structure use the children query parameter:

curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!

The response includes the object tree starting from the specified object ID. Nested child objects are resolved and their values are included in the response:

{ "objectId": "root", "map": { "entries": { "votes": { "data": { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519", "map": { "entries": { "down": { "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669", "counter": { "data": { "number": 10 } } } }, "up": { "data": { "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "counter": { "data": { "number": 5 } } } } } } } } } } }
Copied!

Use root as the object ID in the URL to get the full object tree, or any other object ID to fetch a subset of the tree using that object as the entrypoint.

You can optionally include additional object metadata for all objects included in the response with the metadata query option:

curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true&metadata=true" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!
{ "objectId": "root", "map": { "mapSemantics": "LWW", "entries": { "votes": { "timeserial": "01745828596522-000@e025VxXLABoR0C19591332:001", "tombstone": false, "data": { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519", "map": { "mapSemantics": "LWW", "entries": { "down": { "timeserial": "01745828651671-000@e025VxXLABoR0C19591332:001", "tombstone": false, "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669", "counter": { "data": { "number": 10 } }, "siteTimeserials": { "e02": "01745828651671-000@e025VxXLABoR0C19591332:000" }, "tombstone": false } }, "up": { "timeserial": "01745828645271-000@e025VxXLABoR0C19591332:001", "tombstone": false, "data": { "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "counter": { "data": { "number": 5 } }, "siteTimeserials": { "e02": "01745828645271-000@e025VxXLABoR0C19591332:000" }, "tombstone": false } } } }, "siteTimeserials": { "e02": "01745828651671-000@e025VxXLABoR0C19591332:001" }, "tombstone": false } } } }, "siteTimeserials": { "e02": "01745828596522-000@e025VxXLABoR0C19591332:001" }, "tombstone": false }
Copied!

The tree-structured response can be paginated using the limit parameter to specify the maximum number of objects to include in the response. If a nested child object exists which cannot be included in the response because the limit has been reached, it will be included by reference to its object ID rather than its value:

curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true&limit=1" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!
{ "objectId": "root", "map": { "entries": { "votes": { "data": { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519" } } } } }
Copied!

To obtain the next page, make a subsequent query specifying the referenced object ID as the entrypoint:

curl -X GET "https://rest.ably.io/channels/my-channel/objects/map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519?children=true&limit=1" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!
{ "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519", "map": { "entries": { "down": { "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" } }, "up": { "data": { "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269" } } } } }
Copied!

When using the children query parameter, cyclic references in the object tree will be included as a reference to the object ID rather than including the same object in the response more than once.

For example, if we created a cycle in the object tree by adding a reference to the root object in the votes LiveMap instance with the following operation:

curl -X POST "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '{ "operation": "MAP_SET", "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519", "data": {"key": "myRoot", "value": {"objectId": "root"}} }'
Demo Only
Copied!

The response will handle the cyclic reference by including the myRoot key in the response as a reference to the object ID of the root object:

curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!
{ "objectId": "root", "map": { "entries": { "votes": { "data": { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519", "map": { "entries": { "down": { "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669", "counter": { "data": { "number": 10 } } } }, "up": { "data": { "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "counter": { "data": { "number": 5 } } } }, "myRoot": { "data": { "objectId": "root" } } } } } } } } }
Copied!

GET rest.ably.io/channels/<channelId>/objects/<objectId>/compact

To fetch the objects on the channel in a tree structure in a concise format:

curl -X GET "https://rest.ably.io/channels/my-channel/objects/root/compact" \ -u <loading API key, please wait> -H "Content-Type: application/json"
Demo Only
Copied!

The response includes a compact representation of the object tree that is easy to read:

{ "votes": { "up": 5, "down": 10 } }
Copied!

When using the compact format:

  • LiveMap instances will be represented as a JSON representation of its entries
  • LiveCounter instances will be represented as its numeric value

Cyclic references are handled in the same way as for the tree-structured response. In the example below, the myRoot key references the root object, which is already included in the response:

{ "votes": { "up": 5, "down": 10, "myRoot": { "objectId": "root" } } }
Copied!

The compact format inlines object ID references under the objectId key, allowing references to other objects to be differentiated from string values.

POST rest.ably.io/channels/<channelId>/objects

All operations are published to the same endpoint. The request body specifies:

  • The type of operation to publish
  • The object(s) to which the operation should be applied
  • The operation payload

The request body is of the form:

{ "operation": "<operationType>", "objectId": "<objectId>", "path": "<path>", "data": "<data>" }
Copied!

The operationType is a string that specifies the type of operation to publish and must be one of:

  • MAP_CREATE
  • MAP_SET
  • MAP_REMOVE
  • COUNTER_CREATE
  • COUNTER_INC

Either the objectId or path fields are used to specify the target object(s) for the operation.

The operation payload is provided in the data in accordance with the specified operation type.

To perform operations on a specific object instance, you need to provide its object ID in the request body:

curl -X POST "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '{ "operation": "COUNTER_INC", "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "data": {"number":1} }'
Demo Only
Copied!

The response includes the ID of the published operation message, the channel and a list of object IDs that were affected by the operation:

{ "messageId": "TJPWHhMTrF:0", "channel": "my-channel", "objectIds": ["counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269"] }
Copied!

Path operations provide a convenient way to target objects based on their location in the object tree.

Paths are expressed relative to the structure of the object as defined by the compact view of the object tree.

For example, given the following compact view of the object tree:

The following example increments the LiveCounter instance stored at the up key on the votes LiveMap instance on the root object:

curl -X POST "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '{ "operation": "COUNTER_INC", "path": "votes.up", "data": { "number": 1 } }'
Demo Only
Copied!

You can use wildcards in paths to target multiple objects at once. To increment all LiveCounter instances in the votes LiveMap instance:

curl -X POST "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '{ "operation": "COUNTER_INC", "path": "votes.*", "data": { "number": 1 } }'
Demo Only
Copied!

The response includes the IDs of each of the affected object instances:

{ "messageId": "0Q1w-LpA11:0", "channel": "my-channel", "objectIds": [ "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" ] }
Copied!

Wildcards can be included at the end or in the middle of paths and will match exactly one level in the object tree. For example, given the following compact view of the object tree:

{ "posts": { "post1": { "votes": { "up": 5, "down": 10 } }, "post2": { "votes": { "up": 5, "down": 10 } } } }
Copied!

The following example increments the upvote LiveCounter instances for all posts in the posts LiveMap instance:

curl -X POST "https://sandbox-rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '{ "operation": "COUNTER_INC", "path": "posts.*.votes.up", "data": { "number": 1 } }'
Demo Only
Copied!

If your LiveMap keys contain periods, you can escape them with a backslash. The following example increments the upvote LiveCounter instance for a post with the key post.123:

curl -X POST "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '{ "operation": "COUNTER_INC", "path": "posts.post\.123.votes.up", "data": { "number": 1 } }'
Demo Only
Copied!

Use the MAP_CREATE and COUNTER_CREATE operations to create new objects. You can optionally specify an initial value for the object in the data field when creating it.

For MAP_CREATE, the data field should be a JSON object that contains the initial entries for the LiveMap instance:

{ "operation": "MAP_CREATE", "data": { "title": {"string": "LiveObjects is awesome"}, "createdAt": {"number": 1745835181122}, "isPublished": {"boolean": true} } }
Copied!

For COUNTER_CREATE, the data field should be a JSON object that contains the initial value for the LiveCounter instance:

{ "operation": "COUNTER_CREATE", "data": { "number": 5 } }
Copied!

When you create a new object it is important that the new object is assigned to the object tree so that it is reachable from the root object.

The simplest way to do this is to use the path field in the request body. The path is relative to the root object and specifies where in the object tree the new object should be created.

The following example creates a new LiveMap instance and assigns it to the posts LiveMap instance on the root object under the key post1:

curl -X POST "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '{ "operation": "MAP_CREATE", "path": "posts.post1", "data": { "title": {"string": "LiveObjects is awesome"}, "createdAt": {"number": 1745835181122}, "isPublished": {"boolean": true} } }'
Demo Only
Copied!

When using the path specifier with a COUNTER_CREATE or MAP_CREATE operation, the server constructs two operations which are published as a batch :

  • A MAP_CREATE or COUNTER_CREATE operation used to create the new object
  • A MAP_SET operation used to assign the new object to the LiveMap instance specified by the path

Therefore the response will include the object IDs of all objects affected by the resulting set of operations:

{ "messageId": "mkfjWU2jju:0", "channel": "my-channel", "objectIds": [ "map:cRCKx-eev7Tl66jGfl1SkZh_uEMo6F5jyV0B7mUn4Zs@1745835549101", "map:a_oQqPYUGxi95_Cn0pWcsoeBlHZZtVW5xKIw0hnJCZs@1745835547258" ] }
Copied!

There is no explicit delete operation for objects themselves. Objects that are not reachable from the root map will be eligible for garbage collection.

Remove a reference to a nested object in a LiveMap instance using the MAP_REMOVE operation:

curl -X POST "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '{ "operation": "MAP_REMOVE", "objectId": "root", "data": {"key": "posts"} }'
Demo Only
Copied!

If no other references to the object exist, it will no longer be reachable from the root object and will be eligible for garbage collection.

You can group several operations into a single request by sending an array of operations.

All operations inside the array form a batch operation which is published as a single message. All operations in the batch are processed as a single atomic unit.

The following example shows how to increment two distinct LiveCounter instances in a single batch operation:

curl -X POST "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '[ { "operation": "COUNTER_INC", "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "data": {"number": 1} }, { "operation": "COUNTER_INC", "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669", "data": {"number": 1} } ]'
Demo Only
Copied!

Publish operations idempotently in the same way as for idempotent message publishing by specifying a id for the operation message:

curl -X POST "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '{ "id": "my-idempotency-key", "operation": "COUNTER_INC", "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "data": {"number": 1} }'
Demo Only
Copied!

For batch operations, use the format <baseId>:<index> where the index is the zero-based index of the operation in the array:

curl -X POST "https://rest.ably.io/channels/my-channel/objects" \ -u <loading API key, please wait> \ -H "Content-Type: application/json" \ -d '[ { "id": "my-idempotency-key:0", "operation": "COUNTER_INC", "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", "data": {"number": 1} }, { "id": "my-idempotency-key:1", "operation": "COUNTER_INC", "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669", "data": {"number": 1} } ]'
Demo Only
Copied!
Select...