LiveObjects provides a comprehensive REST API that enables you to directly work with objects without using a client SDK.
Authentication
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.
Fetching objects
The REST API returns objects in two formats:
| Format | Query parameter | Description |
|---|---|---|
| Compact (default) | None | Values-only representation without metadata. Ideal for reading data values. |
| Non-compact | compact=false | Full structure including object IDs and type metadata. Useful for debugging. |
Compact format returns the logical structure of your data as a JSON object. LiveMap instances appear as JSON objects with their entries, and LiveCounter instances appear as numbers.
Non-compact format includes additional metadata for each object:
- Object IDs for each instance
- Object type metadata (map semantics, counter values)
- Complete object hierarchy
Fetch the channel object
Fetch the entire channel object:
curl "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:*****Example compact response:
1
2
3
4
5
6
{
"votes": {
"down": 10,
"up": 5
}
}This example shows a LiveMap stored on the "votes" key of the channel object, which contains two LiveCounter instances on the "down" and "up" keys.
Add compact=false to include object metadata:
curl "https://main.realtime.ably.net/channels/my-channel/object?compact=false" \
-u demokey:*****1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"objectId": "root",
"map": {
"semantics": "LWW",
"entries": {
"votes": {
"data": {
"objectId": "map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692",
"map": {
"semantics": "LWW",
"entries": {
"down": {
"data": {
"objectId": "counter:Yj1F_aEX3T2rRkTkra7Aifmlr8PxUWSR3kO3MzxtQto@1760448653393",
"counter": {
"data": {
"number": 5
}
}
}
},
"up": {
"data": {
"objectId": "counter:ibxddWpDjH8R3cvXWWacfe4IVd3DxT_oqkAafhaS68s@1760448646413",
"counter": {
"data": {
"number": 10
}
}
}
}
}
}
}
}
}
}
}Fetch by path
Return a subset of the channel object by specifying the path query parameter. For example, to return only the votes LiveMap instance from the channel object:
curl "https://main.realtime.ably.net/channels/my-channel/object?path=votes" \
-u demokey:*****Example response:
1
2
3
4
{
"down": 5,
"up": 10
}Fetch by object ID
Fetch a specific object instance by specifying its object ID in the URL path:
curl "https://main.realtime.ably.net/channels/my-channel/object/map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692" \
-u demokey:*****Example compact response:
1
2
3
4
{
"down": 5,
"up": 10
}Add compact=false to include object metadata:
curl "https://main.realtime.ably.net/channels/my-channel/object/map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692?compact=false" \
-u demokey:*****1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"objectId": "map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692",
"map": {
"semantics": "LWW",
"entries": {
"down": {
"data": {
"objectId": "counter:Yj1F_aEX3T2rRkTkra7Aifmlr8PxUWSR3kO3MzxtQto@1760448653393",
"counter": {
"data": {
"number": 5
}
}
}
},
"up": {
"data": {
"objectId": "counter:ibxddWpDjH8R3cvXWWacfe4IVd3DxT_oqkAafhaS68s@1760448646413",
"counter": {
"data": {
"number": 10
}
}
}
}
}
}
}This endpoint also supports the path query option. The path is evaluated relative to the specified object instance:
curl "https://main.realtime.ably.net/channels/my-channel/object/map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692?path=down" \
-u demokey:*****1
5Publishing operations
Publish all operations to the same endpoint: POST /channels/{channelId}/object
Each operation includes:
- A reference to an object using either
objectIdorpath - An operation-specific field (
mapSet,counterInc, etc.) containing the operation parameters
Operations can target objects using either objectId or path:
objectId(string): The unique identifier of the object instance to updatepath(string): The path to the object instance within the channel object
Use dot-separated notation for paths (e.g. votes.up), relative to the channel object. An empty path "" refers to the channel object itself. Paths can contain wildcards (*) to target multiple objects.
Available operations
LiveObjects supports the following operations:
| Operation | Description |
|---|---|
mapSet | Sets a key/value pair in a LiveMap. |
mapRemove | Removes a key from a LiveMap. |
counterInc | Increments or decrements a LiveCounter. |
mapCreate | Creates a new LiveMap instance. |
counterCreate | Creates a new LiveCounter instance. |
To create an object, see Creating Objects.
Each operation has specific required and optional fields:
mapSet
1
2
3
4
5
6
7
{
"path": "user",
"mapSet": {
"key": "username",
"value": {"string": "alice"}
}
}Map values can be any of the supported data value types, including references to other objects.
mapRemove
1
2
3
4
5
6
{
"path": "user",
"mapRemove": {
"key": "username"
}
}counterInc
1
2
3
4
5
6
{
"path": "votes.up",
"counterInc": {
"number": 5
}
}mapCreate
Optionally omit the path or objectId fields when creating an object with mapCreate.
For the object to be reachable in the state tree, assign it to a key in a LiveMap that is reachable from the channel object.
1
2
3
4
5
6
7
8
9
10
11
{
"path": "posts.post1",
"mapCreate": {
"semantics": 0,
"entries": {
"title": {"data": {"string": "LiveObjects is awesome"}},
"createdAt": {"data": {"number": 1745835181122}},
"isPublished": {"data": {"boolean": true}}
}
}
}counterCreate
Optionally omit the path or objectId fields when creating an object with counterCreate.
For the object to be reachable in the state tree, assign it to a key in a LiveMap that is reachable from the channel object.
1
2
3
4
5
6
{
"path": "visits",
"counterCreate": {
"count": 0
}
}Update by object ID
To perform operations on a specific object instance, provide its object ID in the request body:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
"counterInc": {
"number": 1
}
}'The response includes the ID of the published operation message, the channel and a list of object IDs that were affected by the operation:
1
2
3
4
5
{
"messageId": "TJPWHhMTrF:0",
"channel": "my-channel",
"objectIds": ["counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269"]
}Update by path
Path operations target objects based on their location in the channel object.
Paths are expressed relative to the structure of the object as defined by the compact view of the channel object.
The following example increments the LiveCounter instance stored at the up key on the votes LiveMap object:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"path": "votes.up",
"counterInc": {
"number": 1
}
}'Path operations
Path operations provide flexibility when targeting objects.
Path wildcards
Use wildcards in paths to target multiple objects at once. To increment all LiveCounter instances in the votes LiveMap instance:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"path": "votes.*",
"counterInc": {
"number": 1
}
}'The response includes the IDs of each of the affected object instances:
1
2
3
4
5
6
7
8
{
"messageId": "0Q1w-LpA11:0",
"channel": "my-channel",
"objectIds": [
"counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
"counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669"
]
}Wildcards match exactly one level in the channel object and can appear at the end or middle of paths. For example, given the following compact view of the channel object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"posts": {
"post1": {
"votes": {
"up": 5,
"down": 10
}
},
"post2": {
"votes": {
"up": 5,
"down": 10
}
}
}
}The following example increments the upvote LiveCounter instances for all posts in the posts LiveMap instance:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"path": "posts.*.votes.up",
"counterInc": {
"number": 1
}
}'Escaping special characters
If your LiveMap keys contain periods, 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://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"path": "posts.post\.123.votes.up",
"counterInc": {
"number": 1
}
}'Removing objects
Remove an object from the channel using mapRemove to delete the key referencing it:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"objectId": "root",
"mapRemove": {
"key": "posts"
}
}'Map keys can be removed by issuing a mapRemove operation targeting either an objectId or a path.
If no other references to the object exist, it becomes unreachable and is eligible for garbage collection.
Creating objects
Use mapCreate and counterCreate operations to create new LiveObjects instances.
Creating objects with paths
The simplest way to create an object is to specify a path where it should be created. The server automatically creates the object and assigns it to that path in a single atomic operation.
The following example creates a new LiveMap instance and assigns it to the posts LiveMap instance on the channel object under the key post1:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"path": "posts.post1",
"mapCreate": {
"semantics": 0,
"entries": {
"title": {"data": {"string": "LiveObjects is awesome"}},
"createdAt": {"data": {"number": 1745835181122}},
"isPublished": {"data": {"boolean": true}}
}
}
}'When using path with a create operation, the server constructs two operations published as a batch:
- A
mapCreateorcounterCreateoperation to create the new object - A
mapSetoperation to assign the new object to the parentLiveMapat the specified path
This ensures the new object is immediately reachable from the root.
The response will include the object IDs of all objects affected by the resulting set of operations. The newly created object's ID will be the first item in the list:
1
2
3
4
5
6
7
8
{
"messageId": "mkfjWU2jju:0",
"channel": "my-channel",
"objectIds": [
"map:cRCKx-eev7Tl66jGfl1SkZh_uEMo6F5jyV0B7mUn4Zs@1745835549101",
"map:a_oQqPYUGxi95_Cn0pWcsoeBlHZZtVW5xKIw0hnJCZs@1745835547258"
]
}Creating standalone objects
Create objects without immediately assigning them by omitting both objectId and path from the create operation.
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"mapCreate": {
"semantics": 0,
"entries": {
"name": {"data": {"string": "Alice"}}
}
}
}'The response includes the generated object ID:
1
2
3
4
5
{
"messageId": "TJPWHhMTrF:0",
"channel": "my-channel",
"objectIds": ["map:abc123def456...@1745835549101"]
}Client-generated object IDs
Generate object IDs when creating objects to enable atomic batch operations with cross-references between newly created objects. This is useful when creating multiple objects that reference each other in a single batch.
Object IDs are generated by hashing the operation type, the JSON-encoded initial value, and a random nonce. Format: {type}:{hash}@{timestamp}
The hash is a raw (unpadded) URL-safe base64-encoded SHA-256 hash of the concatenation of:
- The initial value bytes (as a JSON encoded string)
- The nonce bytes (as a UTF-8 encoded string)
In the format sha256.Hash(initialValue + ":" + nonce). (i.e. colon separated).
The timestamp is the Unix timestamp in milliseconds at the time of creation.
The type is either map or counter.
There are additional operations for creating objects with client-generated IDs:
mapCreateWithObjectIdcounterCreateWithObjectId
mapCreateWithObjectId
objectId(string): The pre-computed object ID for the new mapinitialValue(string): JSON-encoded string of the map data, matching the format frommapCreate.nonce(string): Random string you generated
1
2
3
4
5
6
7
{
"objectId": "map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168",
"mapCreateWithObjectId": {
"initialValue": "{\"entries\":{\"name\":{\"data\":{\"string\":\"Alice\"}},\"age\":{\"data\":{\"number\":30}}}}",
"nonce": "random-nonce-abc123"
}
}counterCreateWithObjectId
objectId(string): The pre-computed object ID for the new counterinitialValue(string): JSON-encoded string of the counter data, matching the format fromcounterCreate.nonce(string): Random string you generated
1
2
3
4
5
6
7
{
"objectId": "counter:xyz789@ts1",
"counterCreateWithObjectId": {
"initialValue": "{\"count\":0}",
"nonce": "nonce-xyz789"
}
}Publishing with client-generated IDs
Publish an operation using either mapCreateWithObjectId or counterCreateWithObjectId to create
an object with a client-generated ID.
For example:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"objectId": "map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168",
"mapCreateWithObjectId": {
"initialValue": "{\"entries\":{\"name\":{\"data\":{\"string\":\"Alice\"}},\"age\":{\"data\":{\"number\":30}}}}",
"nonce": "random-nonce-abc123"
}
}'Atomic batch with cross-references
Create a map and immediately link it to root in a single atomic operation:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '[
{
"objectId": "map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168",
"mapCreateWithObjectId": {
"initialValue": "{\"entries\":{\"name\":{\"data\":{\"string\":\"Alice\"}}}}",
"nonce": "nonce-1"
}
},
{
"objectId": "root",
"mapSet": {
"key": "alice",
"value": {"objectId": "map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168"}
}
}
]'Both operations execute atomically. The second operation references the object created in the first because you pre-computed the ID.
Cyclic references
For both the full object and the compact response formats, cyclic references in the channel object are included as a reference to the object ID rather than including the same object instance in the response more than once.
For example, if you create a cycle in the channel object by adding a reference to the channel object in the votes LiveMap instance with the following operation:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"objectId": "map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692",
"mapSet": {
"key": "myRoot",
"value": {"objectId": "root"}
}
}'The response will handle the cyclic reference by including the myRoot key in the response as a reference to the object ID of the channel object:
curl "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:*****1
2
3
4
5
6
7
8
9
{
"votes": {
"down": 5,
"up": 10,
"myRoot": {
"objectId": "root"
}
}
}Object reachability and garbage collection
Objects that are not reachable from the channel object will be garbage collected. An object is reachable if it can be accessed by traversing keys starting from the channel object.
When you remove an object using mapRemove, the object becomes unreachable if no other references to it exist. Unreachable objects are automatically deleted by garbage collection.
Batch operations
Group multiple operations into a single request by sending an array of operations. All operations are published as a single message and processed as a single atomic unit. Learn more about batch operations.
The following example increments two distinct LiveCounter instances in a single batch operation:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '[
{
"objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
"counterInc": {
"number": 1
}
},
{
"objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669",
"counterInc": {
"number": 1
}
}
]'Idempotent operations
Publish operations idempotently by specifying an id for the operation message, using the same approach as idempotent message publishing:
curl -X POST "https://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '{
"id": "my-idempotency-key",
"objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
"counterInc": {
"number": 1
}
}'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://main.realtime.ably.net/channels/my-channel/object" \
-u demokey:***** \
-H "Content-Type: application/json" \
-d '[
{
"id": "my-idempotency-key:0",
"objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
"counterInc": {
"number": 1
}
},
{
"id": "my-idempotency-key:1",
"objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669",
"counterInc": {
"number": 1
}
}
]'Data values reference
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. Examples of data value formats:
1
2
3
4
5
6
{ "data": { "number": 42 } }
{ "data": { "string": "LiveObjects is awesome" } }
{ "data": { "boolean": true } }
{ "data": { "bytes": "TGl2ZU9iamVjdHMgaXMgYXdlc29tZQo=" } }
{ "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" } }
{ "data": { "json": "{\"someKey\": \"someValue\"}" } }