Using the REST API

Open in

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:

FormatQuery parameterDescription
Compact (default)NoneValues-only representation without metadata. Ideal for reading data values.
Non-compactcompact=falseFull 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:

JSON

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:*****
JSON

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:

JSON

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:

JSON

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:*****
JSON

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:*****
JSON

1

5

Publishing operations

Publish all operations to the same endpoint: POST /channels/{channelId}/object

Each operation includes:

  1. A reference to an object using either objectId or path
  2. 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 update
  • path (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:

OperationDescription
mapSetSets a key/value pair in a LiveMap.
mapRemoveRemoves a key from a LiveMap.
counterIncIncrements or decrements a LiveCounter.
mapCreateCreates a new LiveMap instance.
counterCreateCreates a new LiveCounter instance.

To create an object, see Creating Objects.

Each operation has specific required and optional fields:

mapSet

JSON

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

JSON

1

2

3

4

5

6

{
  "path": "user",
  "mapRemove": {
    "key": "username"
  }
}

counterInc

JSON

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.

JSON

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.

JSON

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:

JSON

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:

JSON

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:

JSON

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:

  1. A mapCreate or counterCreate operation to create the new object
  2. A mapSet operation to assign the new object to the parent LiveMap at 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:

JSON

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:

JSON

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:

  • mapCreateWithObjectId
  • counterCreateWithObjectId

mapCreateWithObjectId

  • objectId (string): The pre-computed object ID for the new map
  • initialValue (string): JSON-encoded string of the map data, matching the format from mapCreate.
  • nonce (string): Random string you generated
JSON

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 counter
  • initialValue (string): JSON-encoded string of the counter data, matching the format from counterCreate.
  • nonce (string): Random string you generated
JSON

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:*****
JSON

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:

JSON

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\"}" } }