Using the REST SDK

Open in

LiveObjects provides a JavaScript REST SDK that enables you to directly work with objects without using a realtime connection.

Setup

Create an Ably.Rest client instance with the LiveObjects plugin:

JavaScript

1

2

3

import { LiveObjects } from 'ably/liveobjects';

const rest = new Ably.Rest({ plugins: { LiveObjects }, /* other ClientOptions */ });

The LiveObjects REST SDK is then available on a channel via channel.object:

JavaScript

1

2

const channel = rest.channels.get('my-channel');
channel.object // LiveObjects REST SDK

Authentication

Authentication is configured when instantiating the REST client. Pass an API key or use token authentication via the ClientOptions. See the REST SDK authentication documentation for details.

To read objects on a channel, an API key must have the object-subscribe capability. With only this capability, clients have read-only access, preventing them from publishing operations.

To create or update objects, the API key must have the object-publish capability. Include both object-subscribe and object-publish for full read and write access.

Fetch objects

Use channel.object.get

get(RestObjectGetParams params?): Promise<Object>

Reads object data from the channel. If no objectId is provided then the entire channel object is returned. Makes a request to the GET /channels/{channelId}/object REST API endpoint. The return type depends on the compact parameter: when compact is true (default), returns a RestObjectGetCompactResult; when compact is false, returns a RestObjectGetFullResult.

ParameterDescriptionType
paramsAn optional object containing the query parametersRestObjectGetParams

Objects can be fetched in two formats:

FormatParameterDescription
Compact (default)compact: trueValues-only representation without metadata. Ideal for reading data values.
Fullcompact: falseFull structure including object IDs and type metadata. Useful for debugging.

Compact format returns the logical structure of your data as a JSON-like value. LiveMap instances appear as JSON objects with their entries, and LiveCounter instances appear as numbers. bytes-typed values are returned as ArrayBuffer when using the binary protocol, or as base64-encoded strings when using the JSON protocol. json-typed values remain as JSON-encoded strings.

Full format includes additional metadata for each object:

  • Object IDs for each instance
  • Object type metadata (map semantics, counter values)
  • Complete object hierarchy

Since each value in the full format carries explicit type information, the SDK always decodes bytes values to ArrayBuffer and json values to native objects or arrays.

See Data values reference for more details on value types and encoding.

Fetch the channel object

Fetch the entire channel object:

JavaScript

1

const data = await channel.object.get();

Example compact result:

JSON

1

2

3

4

5

6

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

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.

Set compact: false to include object metadata:

JavaScript

1

const data = await channel.object.get({ compact: false });
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 parameter. For example, to return only the votes LiveMap instance from the channel object:

JavaScript

1

const data = await channel.object.get({ path: 'votes' });

Example result:

JSON

1

2

3

4

{
  "down": 5,
  "up": 10
}

Fetch by object ID

Fetch a specific object instance by specifying its objectId:

JavaScript

1

2

3

const data = await channel.object.get({
  objectId: 'map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692'
});

Example compact result:

JSON

1

2

3

4

{
  "down": 5,
  "up": 10
}

Set compact: false to include object metadata:

JavaScript

1

2

3

4

const data = await channel.object.get({
  objectId: 'map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692',
  compact: false
});
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
            }
          }
        }
      }
    }
  }
}

You can also specify a path parameter alongside objectId. The path is evaluated relative to the specified object instance:

JavaScript

1

2

3

4

const data = await channel.object.get({
  objectId: 'map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692',
  path: 'down'
});
JSON

1

5

Publish operations

Use channel.object.publish

publish(RestObjectOperation operation): Promise<RestObjectPublishResult>

publish(RestObjectOperation[] operations): Promise<RestObjectPublishResult>

Publishes one or more operations to modify objects on the channel. Makes a request to the POST /channels/{channelId}/object REST API endpoint. When an array is provided, all operations are published as an atomic batch.

ParameterDescriptionType
operationThe operation or array of operations to publishRestObjectOperation or RestObjectOperation[]

Returns a RestObjectPublishResult containing the message ID and affected object IDs.

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 objectId or path:

  • objectId (string): The unique identifier of the object instance to create or update
  • path (string): The path to the object instance within the channel object

Use dot-separated notation for paths (for example 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.
mapCreateWithObjectIdCreates a new LiveMap with a client-generated ID.
counterCreateWithObjectIdCreates a new LiveCounter with a client-generated ID.

To create an object, see Create objects.

Each operation has specific required and optional fields:

mapSet

JavaScript

1

2

3

4

5

6

7

await channel.object.publish({
  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

JavaScript

1

2

3

4

5

6

await channel.object.publish({
  path: 'user',
  mapRemove: {
    key: 'username'
  }
});

counterInc

JavaScript

1

2

3

4

5

6

await channel.object.publish({
  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.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

await channel.object.publish({
  path: 'posts.post1',
  mapCreate: {
    semantics: 'lww',
    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.

JavaScript

1

2

3

4

5

6

await channel.object.publish({
  path: 'visits',
  counterCreate: {
    count: 0
  }
});

Update by object ID

To perform operations on a specific object instance, provide its objectId in the operation:

JavaScript

1

2

3

4

5

6

const result = await channel.object.publish({
  objectId: 'counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269',
  counterInc: {
    number: 1
  }
});

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:

JavaScript

1

2

3

4

5

6

const result = await channel.object.publish({
  path: 'votes.up',
  counterInc: {
    number: 1
  }
});

Publish result

The result 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"]
}

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:

JavaScript

1

2

3

4

5

6

const result = await channel.object.publish({
  path: 'votes.*',
  counterInc: {
    number: 1
  }
});

The result 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": {
        "down": 5,
        "up": 10
      }
    },
    "post2": {
      "votes": {
        "down": 5,
        "up": 10
      }
    }
  }
}

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

JavaScript

1

2

3

4

5

6

const result = await channel.object.publish({
  path: 'posts.*.votes.up',
  counterInc: {
    number: 1
  }
});

Escape 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:

JavaScript

1

2

3

4

5

6

const result = await channel.object.publish({
  path: 'posts.post\\.123.votes.up',
  counterInc: {
    number: 1
  }
});

Remove objects

Remove an object from the channel using mapRemove to delete the key referencing it:

JavaScript

1

2

3

4

5

6

await channel.object.publish({
  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.

Create objects

Use mapCreate and counterCreate operations to create new LiveObjects instances.

Create 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:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

const result = await channel.object.publish({
  path: 'posts.post1',
  mapCreate: {
    semantics: 'lww',
    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 channel object.

The result 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"
  ]
}

Client-generated object IDs

Client-generated object IDs enable atomic batch operations with cross-references between newly created objects. Use channel.object.generateObjectId to generate an object ID, then use mapCreateWithObjectId or counterCreateWithObjectId to create the object with that ID. See the REST API documentation for details on the ID generation algorithm.

Use channel.object.generateObjectId

generateObjectId(RestObjectOperationMapCreateBody | RestObjectOperationCounterCreateBody createBody): Promise<RestObjectGenerateIdResult>

Generates an object ID for a create operation. Pass a RestObjectOperationMapCreateBody or RestObjectOperationCounterCreateBody to specify the object type and initial value. Returns a RestObjectGenerateIdResult containing the generated objectId, nonce, and initialValue needed to construct a mapCreateWithObjectId or counterCreateWithObjectId operation.

The following example generates an object ID for a new map, then creates and assigns it to the channel object in a single atomic batch:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// Generate an object ID for a new map
const { objectId, nonce, initialValue } = await channel.object.generateObjectId({
  mapCreate: {
    semantics: 'lww',
    entries: {
      name: { data: { string: 'Alice' } }
    }
  }
});

// Create the map and assign it to the channel object atomically
const result = await channel.object.publish([
  {
    objectId,
    mapCreateWithObjectId: { initialValue, nonce }
  },
  {
    objectId: 'root',
    mapSet: {
      key: 'alice',
      value: { objectId }
    }
  }
]);

Both operations execute atomically. The second operation references the object created in the first because the ID was pre-computed with generateObjectId.

Cyclic references

For both the full object and the compact 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 result 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:

JavaScript

1

2

3

4

5

6

7

await channel.object.publish({
  objectId: 'map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692',
  mapSet: {
    key: 'myObject',
    value: { objectId: 'root' }
  }
});

The result will handle the cyclic reference by including the myObject key as a reference to the object ID of the channel object:

JavaScript

1

const data = await channel.object.get();
JSON

1

2

3

4

5

6

7

8

9

{
  "votes": {
    "down": 5,
    "up": 10,
    "myObject": {
      "objectId": "root"
    }
  }
}

Object reachability and garbage collection

Objects that are not reachable from the channel object will be automatically garbage collected. See the REST API documentation and reachability and object lifecycle for details.

Batch operations

Group multiple operations into a single call by passing an array of operations to publish(). 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:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

await channel.object.publish([
  {
    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, using the same approach as idempotent message publishing:

JavaScript

1

2

3

4

5

6

7

await channel.object.publish({
  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:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

await channel.object.publish([
  {
    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 SDK, primitive types and object references are represented as typed value objects.

The key in the value object indicates the type of the value. Examples of data value formats:

JavaScript

1

2

3

4

5

6

{ number: 42 }
{ string: 'LiveObjects is awesome' }
{ boolean: true }
{ bytes: new Uint8Array([76, 105, 118, 101]) }
{ objectId: 'counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669' }
{ json: { someKey: 'someValue' } }

RestObjectGetParams

Parameters for the get() method:

PropertyDescriptionType
objectIdThe unique identifier of the object instance to fetch. If omitted, fetches from the channel objectString (optional)
pathA dot-separated path to return a subset of the object. Evaluated relative to the channel object or the specified objectIdString (optional)
compactWhen true (default), returns a values-only representation. When false, includes object IDs and type metadataBoolean (optional)

RestObjectOperation

An operation passed to the publish() method. Each operation contains an optional id for idempotent publishing, an optional target (objectId or path), and exactly one operation-specific field containing the operation parameters:

PropertyDescriptionType
idIdentifier for idempotent publishingString (optional)
objectIdThe unique identifier of the object instance to targetString (optional)
pathThe dot-separated path to the object instance within the channel object. Evaluated relative to the channel objectString (optional)
mapSetParameters for setting a key to a value in a LiveMapRestObjectOperationMapSet (optional)
mapRemoveParameters for removing a key from a LiveMapRestObjectOperationMapRemove (optional)
counterIncParameters for incrementing a LiveCounterRestObjectOperationCounterInc (optional)
mapCreateParameters for creating a new LiveMapRestObjectOperationMapCreate (optional)
counterCreateParameters for creating a new LiveCounterRestObjectOperationCounterCreate (optional)
mapCreateWithObjectIdParameters for creating a new LiveMap with a client-generated IDRestObjectOperationMapCreateWithObjectId (optional)
counterCreateWithObjectIdParameters for creating a new LiveCounter with a client-generated IDRestObjectOperationCounterCreateWithObjectId (optional)

RestObjectOperationMapSet

PropertyDescriptionType
keyThe key to setString
valueThe value to assign to the keyPublishObjectData

RestObjectOperationMapRemove

PropertyDescriptionType
keyThe key to removeString

RestObjectOperationCounterInc

PropertyDescriptionType
numberThe amount to increment by. Use a negative value to decrementNumber

RestObjectOperationMapCreate

PropertyDescriptionType
semanticsThe conflict-resolution semantics for the map. One of: 'lww'String
entriesInitial key-value pairs, keyed by stringRecord<String, { data: PublishObjectData }>

RestObjectOperationCounterCreate

PropertyDescriptionType
countThe initial value of the counterNumber

RestObjectOperationMapCreateWithObjectId

Use generateObjectId() to get the initialValue and nonce values for this operation.

PropertyDescriptionType
initialValueJSON-encoded string of the mapCreate object. Binary values in entries must be base64-encodedString
nonceRandom string used to generate the object IDString

RestObjectOperationCounterCreateWithObjectId

Use generateObjectId() to get the initialValue and nonce values for this operation.

PropertyDescriptionType
initialValueJSON-encoded string of the counterCreate objectString
nonceRandom string used to generate the object IDString

RestObjectGetCompactResult

Returned by get() when compact is true (default). The result is a recursive type: null, boolean, number, string, binary data (ArrayBuffer when using the binary protocol, or a base64-encoded string when using the JSON protocol), or a JSON object whose values are themselves RestObjectGetCompactResult. LiveMap instances appear as JSON objects with their entries, and LiveCounter instances appear as numbers.

RestObjectGetFullResult

Returned by get() when compact is false. Can be a RestLiveObject (full object with metadata) or a RestObjectData (leaf value when the path resolves to a primitive entry in a map).

RestLiveObject

A full object structure including object IDs and type metadata. Can be a RestLiveMap, RestLiveCounter, or a generic object with an objectId property.

RestLiveMap

PropertyDescriptionType
objectIdThe ID of the map objectString
mapThe map dataRestLiveMapValue

RestLiveMapValue

PropertyDescriptionType
semanticsThe conflict-resolution semantics. One of: 'lww'String
entriesThe map entries, indexed by key. Each entry is either a RestObjectDataMapEntry (leaf value) or a RestLiveObjectMapEntry (nested object)Record<String, RestObjectDataMapEntry | RestLiveObjectMapEntry>

RestObjectDataMapEntry

PropertyDescriptionType
dataThe value for this entryRestObjectData

RestLiveObjectMapEntry

PropertyDescriptionType
dataA nested objectRestLiveObject

RestLiveCounter

PropertyDescriptionType
objectIdThe ID of the counter objectString
counterThe counter dataRestLiveCounterValue

RestLiveCounterValue

PropertyDescriptionType
dataHolds the counter value{ number: Number }

RestObjectData

Represents a leaf data value for an object. Either a primitive value or a reference to another object. Exactly one property is set, indicating the type:

PropertyDescriptionType
stringA string valueString
numberA numeric valueNumber
booleanA boolean valueBoolean
bytesA binary valueArrayBuffer
jsonA JSON value (array or object)Array / Object
objectIdA reference to another object by its IDString

PublishObjectData

Same properties as RestObjectData. Used when publishing operations via publish(). Exactly one property must be set.

RestObjectPublishResult

Result returned by the publish() method:

PropertyDescriptionType
messageIdThe ID of the message containing the published operationsString
channelThe name of the channel the operations were published toString
objectIdsArray of object IDs affected by the operations. May include multiple IDs for wildcard paths and batch operationsString[]

RestObjectOperationMapCreateBody

PropertyDescriptionType
mapCreateParameters for creating a new LiveMapRestObjectOperationMapCreate

RestObjectOperationCounterCreateBody

PropertyDescriptionType
counterCreateParameters for creating a new LiveCounterRestObjectOperationCounterCreate

RestObjectGenerateIdResult

Result returned by generateObjectId():

PropertyDescriptionType
objectIdThe generated object IDString
nonceThe nonce used in ID generationString
initialValueThe JSON-encoded initial value used in ID generationString