diff --git a/content/api/realtime-sdk/channels.textile b/content/api/realtime-sdk/channels.textile
index 13753b7d28..02adb6a54e 100644
--- a/content/api/realtime-sdk/channels.textile
+++ b/content/api/realtime-sdk/channels.textile
@@ -151,6 +151,11 @@ h6(#push).
Provides access to the "PushChannel":/docs/api/realtime-sdk/push#push-channel object for this channel which can be used to access members present on the channel, or participate in presence.
+h6(#objects).
+ default: objects
+
+Provides access to the "Objects":/docs/liveobjects object for this channel which can be used to read, modify and subscribe to LiveObjects on a channel.
+
h3. Methods
h6(#publish).
diff --git a/content/auth/capabilities.textile b/content/auth/capabilities.textile
index b5d04ad908..d8e189a954 100644
--- a/content/auth/capabilities.textile
+++ b/content/auth/capabilities.textile
@@ -42,6 +42,8 @@ The following capability operations are available for API keys and issued tokens
- subscribe := can subscribe to messages and presence state change messages on channels, and get the presence set of a channel
- publish := can publish messages to channels
- presence := can register presence on a channel (enter, update and leave)
+- object-subscribe := can subscribe to updates to objects on a channel
+- object-publish := can update objects on a channel
- history := can retrieve message and presence state history on channels
- stats := can retrieve current and historical usage statistics for an app
- push-subscribe := can subscribe devices for push notifications
diff --git a/content/channels/options/index.textile b/content/channels/options/index.textile
index 114bc7924e..7ecb5fa953 100644
--- a/content/channels/options/index.textile
+++ b/content/channels/options/index.textile
@@ -234,7 +234,7 @@ await channel.setOptions(channelOptions);
h2(#params). Params
-The @params@ property can be used to enable additional features on a channel-by-channel basis. These features include using @rewind@ to replay messages published prior to a client attaching to a channel, @delta@ so that message payloads only contain the difference between the current and previous message, and @occupancy@ to subscribe to metrics about the clients attached to a channel.
+The @params@ property can be used to enable additional features on a channel-by-channel basis.
h3(#rewind). Rewind
@@ -380,7 +380,9 @@ The following is an example of an occupancy metric event:
subscribers: 1,
presenceConnections: 1,
presenceMembers: 0,
- presenceSubscribers: 1
+ presenceSubscribers: 1,
+ objectPublishers: 1,
+ objectSubscribers: 1
}
},
encoding: null,
@@ -402,17 +404,46 @@ Occupancy events have a payload in the @data@ property with a value of @occupanc
}
```
+h3(#objects). Inband Objects
+
+"Inband objects":/docs/liveobjects/inband-objects allows clients to subscribe to changes to "LiveObjects":/docs/liveobjects channel objects as regular channel messages.
+
+When using inband objects, the client receives messages with the special name @[meta]objects@ that describe the current set of objects on a channel.
+
+
+
+For more information see the "inband objects":/docs/liveobjects/inband-objects documentation.
+
h2(#modes). Modes
-The @modes@ property can be used to set channel mode flags. Channel mode flags enable a client to specify a subset of the "capabilities":/docs/auth/capabilities granted by their token or API key. Channel mode flags offer the ability for clients to use different capabilities for different channels, however, as they are flags and not permissions they cannot be enforced by an authentication server.
+Channel mode flags enable a client to specify which functionality they will use on the channel.
+
+A client can explicitly request a set of modes using the @modes@ property. If the @modes@ property is not provided, the default modes will be used.
+
+The available set of channel mode flags are:
+
+|_. Flag |_. Description |_. Default? |
+| @SUBSCRIBE@ | Can subscribe to receive messages on the channel. | Yes |
+| @PUBLISH@ | Can publish messages to the channel. | Yes |
+| @PRESENCE_SUBSCRIBE@ | Can subscribe to receive presence events on the channel. | Yes |
+| @PRESENCE@ | Can register presence on the channel. | Yes |
+| @OBJECT_PUBLISH@ | Can update objects on the channel. | No |
+| @OBJECT_SUBSCRIBE@ | Can subscribe to receive updates to objects on the channel. | No |
+
+The set of modes available to a client is determined by the set of "capabilities":/docs/auth/capabilities granted by their token or API key.
+
+The modes granted by each capability are:
-The channel mode flags available are:
+|_. Capability |_. Granted Modes |
+| @subscribe@ | @SUBSCRIBE@, @PRESENCE_SUBSCRIBE@, @OBJECT_SUBSCRIBE@ |
+| @publish@ | @PUBLISH@ |
+| @presence@ | @PRESENCE@ |
+| @object-subscribe@ | @OBJECT_SUBSCRIBE@ |
+| @object-publish@ | @OBJECT_PUBLISH@ |
-|_. Flag |_. Description |
-| SUBSCRIBE | Can subscribe to receive messages on the channel. |
-| PUBLISH | Can publish messages to the channel. |
-| PRESENCE_SUBSCRIBE | Can subscribe to receive presence events on the channel. |
-| PRESENCE | Can register presence on the channel. |
+The actual modes assigned to a client will be the **intersection** of the requested @modes@ and the modes available to the client according to its capabilities. For example, a client with the @subscribe@ capability which explicitly requests @SUBSCRIBE@ and @PUBLISH@ modes will be assigned only the @SUBSCRIBE@ mode.
The following is an example of setting channel mode flags:
diff --git a/content/channels/options/rewind.textile b/content/channels/options/rewind.textile
index 6fd1c3f107..4351fd9d05 100644
--- a/content/channels/options/rewind.textile
+++ b/content/channels/options/rewind.textile
@@ -41,7 +41,7 @@ If the attachment is successful, and one or more messages exist on the channel p
Any @rewind@ value that cannot be parsed either as a number or a time specifier will cause the attachment request to fail and return an error.
The following example subscribes to a channel and retrieves the most recent message sent on it, if available:
diff --git a/content/integrations/webhooks/index.textile b/content/integrations/webhooks/index.textile
index a65b162cf3..1498bc6f55 100644
--- a/content/integrations/webhooks/index.textile
+++ b/content/integrations/webhooks/index.textile
@@ -415,7 +415,9 @@ The following is an example of a batched @channel lifecycle@ payload:
"subscribers": 1,
"presenceConnections": 1,
"presenceMembers": 0,
- "presenceSubscribers": 1
+ "presenceSubscribers": 1,
+ "objectPublishers": 1,
+ "objectSubscribers": 1
}
}
}
diff --git a/content/liveobjects/batch.textile b/content/liveobjects/batch.textile
new file mode 100644
index 0000000000..03cc3a5fbc
--- /dev/null
+++ b/content/liveobjects/batch.textile
@@ -0,0 +1,152 @@
+---
+title: Batch operations
+meta_description: "Group multiple objects operations into a single channel message to apply grouped operations atomically and improve performance."
+product: liveobjects
+languages:
+ - javascript
+---
+
+
+
+The Batching API in LiveObjects enables multiple updates to be grouped into a single channel message and applied atomically. It ensures that all operations in a batch either succeed together or are discarded entirely. Batching operations is essential when multiple related updates to channel objects must be applied as a single atomic unit, for example, when application logic depends on multiple objects being updated simultaneously. Without batching, if one operation succeeds while another fails, your application state could become inconsistent.
+
+Note that this differs from "Message batching":/docs/messages/batch, the native Pub/Sub messages feature. The LiveObjects Batching API is a separate API specifically designed to enable you to group object operations into a single channel message, ensuring that the Ably system guarantees the atomicity of the applied changes.
+
+h2(#create). Create batch context
+
+blang[javascript].
+
+ To batch object operations together, use the @channel.objects.batch()@ method. This method accepts a callback function, which is provided with a batch context object. The batch context object provides a synchronous API to work with objects on a channel that stores operations inside the batch instead of applying them immediately.
+
+ Using the batch context ensures that operations are grouped and sent in a single channel message after the batch callback function has run. This guarantees that all changes are applied atomically by both the server and all clients.
+
+
+
+blang[javascript].
+
+ ```[javascript]
+ await channel.objects.batch((ctx) => {
+ const root = ctx.getRoot();
+
+ root.set('foo', 'bar');
+ root.set('baz', 42);
+
+ const counter = root.get('counter');
+ counter.increment(5);
+
+ // Batched operations are sent to the Ably system when the batch callback has run.
+ });
+ ```
+
+If an error occurs within the batch, all operations are discarded, preventing partial updates and ensuring atomicity.
+
+h3(#context). Batch context object
+
+blang[javascript].
+
+ The batch context provides a synchronous API for objects operations inside the batch callback. It mirrors the asynchronous API found on @channel.objects@, including "LiveCounter":/docs/liveobjects/counter and "LiveMap":/docs/liveobjects/map.
+
+ To access the batch API, call @BatchContext.getRoot()@, which synchronously returns a wrapper around the "root":/docs/liveobjects/concepts/objects#root-object object instance. This wrapper enables you to access and modify objects within a batch.
+
+
+
+blang[javascript].
+
+ ```[javascript]
+ await channel.objects.batch((ctx) => {
+ // Note: .getRoot() call on a batch context is synchronous.
+ // The returned root object is a special wrapper around a regular LiveMap instance,
+ // providing a synchronous mutation API.
+ const root = ctx.getRoot();
+
+ // Mutation operations like LiveMap.set and LiveCounter.increment
+ // are synchronous inside the batch and queue operations instead of applying them immediately.
+ root.set('foo', 'bar');
+ root.remove('baz');
+
+ // Access other objects through the root object from the BatchContext.getRoot() method.
+ const counter = root.get('counter');
+ counter.increment(5);
+ });
+ ```
+
+You cannot create new objects using the batch context. If you need to create new objects and add them to the channel as part of an atomic batch operation to guarantee atomicity, you must first create them using the regular @channel.objects@ API. Once the objects have been created, you can then assign them to the object tree inside a batch function.
+
+blang[javascript].
+
+ ```[javascript]
+ // First, create new objects outside the batch context
+ const counter = await channel.objects.createCounter();
+ const map = await channel.objects.createMap();
+
+ // Then, use a batch to assign them atomically to the channel objects
+ await channel.objects.batch((ctx) => {
+ const root = ctx.getRoot();
+ root.set('counter', counter);
+ root.set('map', map);
+ });
+ ```
+
+h3(#use-cases). When to batch operations
+
+Usually, you don't need to use batching for objects operations. It is only useful in situations where a group of operations must be applied together to maintain consistency in application state, or when there are multiple mutation operations that you might want to apply at the same time to improve the UI experience.
+
+For example, in a task dashboard application, you might want to remove all tasks on a board in a single operation to prevent excessive UI updates that the user would otherwise experience.
+
+blang[javascript].
+
+ ```[javascript]
+ await channel.objects.batch((ctx) => {
+ const root = ctx.getRoot();
+ const tasks = root.get('tasks');
+
+ for (const key of reactions.keys()) {
+ reactions.remove(key);
+ }
+ });
+ ```
+
+h3(#cancel). Cancel batch operation
+
+To explicitly cancel a batch before it is applied, throw an error inside the batch function. This prevents any queued operations from being applied.
+
+blang[javascript].
+
+ ```[javascript]
+ await channel.objects.batch((ctx) => {
+ const root = ctx.getRoot();
+ root.set('foo', 'bar');
+
+ // Throwing an error prevents any queued operations from being applied.
+ throw new Error('Cancel batch');
+ });
+ ```
+
+blang[javascript].
+
+ h3(#closed). Batch API cannot be used outside the callback function
+
+ The Batch API provided by the batch context object cannot be used outside the callback function. Attempting to do so results in an error. This applies both to @BatchContext.getRoot()@ and any object instances retrieved from it.
+
+blang[javascript].
+
+ ```[javascript]
+ let root;
+ await channel.objects.batch((ctx) => {
+ root = ctx.getRoot();
+ });
+
+ // Calling any Batch API methods outside the batch callback
+ // will throw an Error: Batch is closed.
+ root.set('foo', 'bar');
+ ```
diff --git a/content/liveobjects/concepts/objects.textile b/content/liveobjects/concepts/objects.textile
new file mode 100644
index 0000000000..a718237540
--- /dev/null
+++ b/content/liveobjects/concepts/objects.textile
@@ -0,0 +1,258 @@
+---
+title: Objects
+meta_description: "Learn how data is represented as objects in Ably LiveObjects"
+product: liveobjects
+languages:
+ - javascript
+---
+
+
+
+LiveObjects enables you to store shared data as "objects" on a channel, allowing your application data to be synchronized across multiple users and devices in realtime. This document explains the key concepts you need to know when working with objects.
+
+h2(#object-types). Object Types
+
+LiveObjects provides specialized object types to model your application state. These object types are designed to be conflict-free and eventually consistent, meaning that all operations on them are commutative and converge to the same state across all clients.
+
+h3(#livemap). LiveMap Object
+
+"LiveMap":/docs/liveobjects/map is a key/value data structure similar to a dictionary or JavaScript "Map":https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map :
+
+* Keys must be strings
+* Values can be primitive types or "references":#composability to other objects
+* Supports @set@ and @remove@ operations
+* Concurrent updates to the same key are resolved using last-write-wins (LWW) semantics
+
+blang[javascript].
+
+ ```[javascript]
+ // Create a LiveMap
+ const userSettings = await channel.objects.createMap();
+
+ // Set primitive values
+ await userSettings.set('theme', 'dark');
+ await userSettings.set('notifications', true);
+ ```
+
+h4(#primitive-types). Primitive Types
+
+"LiveMap":/docs/liveobjects/map supports the following primitive types as values:
+
+* @string@
+* @number@
+* @boolean@
+* @bytes@
+
+
+
+h3(#livecounter). LiveCounter Object
+
+"LiveCounter":/docs/liveobjects/counter is a numeric counter type:
+
+* The value is a double-precision floating-point number
+* Supports @increment@ and @decrement@ operations
+
+blang[javascript].
+
+ ```[javascript]
+ // Create a LiveCounter
+ const visitsCounter = await channel.objects.createCounter();
+
+ // Increment the counter
+ await visitsCounter.increment(1);
+ ```
+
+h3(#root-object). Root Object
+
+The root object is a special @LiveMap@ instance which:
+
+* Implicitly exists on a channel and does not need to be created explicitly
+* Has the special "objectId":#object-ids of @root@
+* Cannot be deleted
+* Serves as the "entry point":#reachability for accessing all other objects on a channel
+
+Access the root object using the @getRoot()@ function:
+
+blang[javascript].
+
+ ```[javascript]
+ // Get the Root Object
+ const root = await channel.objects.getRoot();
+
+ // Use it like any other LiveMap
+ await root.set('app-version', '1.0.0');
+ ```
+
+h2(#reachability). Reachability
+
+All objects must be reachable from the root object (directly or indirectly). Objects that cannot be reached from the root object will eventually "be deleted":/docs/liveobjects/lifecycle#objects-deleted .
+
+When an object has been deleted, it is no longer usable and calling any methods on the object will fail.
+
+In the example below, the only reference to the @counterOld@ object is replaced on the @root@. This makes @counterOld@ unreachable and it will eventually be deleted.
+
+blang[javascript].
+
+ ```[javascript]
+ // Create a counter and reference it from the root
+ const counterOld = await channel.objects.createCounter();
+ await root.set('myCounter', counterOld);
+
+ // counterOld will eventually be deleted
+ counterOld.on('deleted', () => {
+ console.log('counterOld has been deleted and can no longer be used');
+ });
+
+ // Create a new counter and replace the old one referenced from the root
+ const counterNew = await channel.objects.createCounter();
+ await root.set('myCounter', counterNew);
+ ```
+
+
+
+h2(#composability). Composability
+
+LiveObjects enables you to build complex, hierarchical data structures through composability.
+
+Specifically, a LiveMap:/docs/liveobjects/map can store references to other @LiveMap@ or @LiveCounter@ object instances as values. This allows you to create nested hierarchies of data.
+
+blang[javascript].
+
+ ```[javascript]
+ // Create LiveObjects
+ const profileMap = await channel.objects.createMap();
+ const preferencesMap = await channel.objects.createMap();
+ const activityCounter = await channel.objects.createCounter();
+
+ // Build a composite structure
+ await preferencesMap.set('theme', 'dark');
+ await profileMap.set('preferences', preferencesMap);
+ await profileMap.set('activity', activityCounter);
+ await root.set('profile', profileMap);
+
+ // Resulting structure:
+ // root (LiveMap)
+ // └── profile (LiveMap)
+ // ├── preferences (LiveMap)
+ // │ └── theme: "dark" (string)
+ // └── activity (LiveCounter)
+ ```
+
+
+
+It is possible for the same object instance to be accessed from multiple places in your object tree:
+
+blang[javascript].
+
+ ```[javascript]
+ // Create a counter
+ const counter = await channel.objects.createCounter();
+
+ // Create two different maps
+ const mapA = await channel.objects.createMap();
+ const mapB = await channel.objects.createMap();
+ await root.set('a', mapA);
+ await root.set('b', mapB);
+
+ // Reference the same counter from both maps
+ await mapA.set('count', counter);
+ await mapB.set('count', counter);
+
+ // The counter referenced from each location shows the same
+ // value, since they refer to the same underlying counter
+ mapA.get('count').subscribe(() => {
+ console.log(mapA.get('count').value()); // 1
+ });
+ mapB.get('count').subscribe(() => {
+ console.log(mapB.get('count').value()); // 1
+ });
+
+ // Increment the counter
+ await counter.increment(1);
+ ```
+
+It is also possible that object references form a cycle:
+
+blang[javascript].
+
+ ```[javascript]
+ // Create two different maps
+ const mapA = await channel.objects.createMap();
+ const mapB = await channel.objects.createMap();
+
+ // Set up a circular reference
+ await mapA.set('ref', mapB);
+ await mapB.set('ref', mapA);
+
+ // Add one map to root (both are now reachable)
+ await root.set('a', mapA);
+
+ // We can traverse the cycle
+ root.get('a') // mapA
+ .get('ref') // mapB
+ .get('ref'); // mapA
+ ```
+
+
+h2(#metadata). Metadata
+
+Objects include metadata that helps with synchronization, conflict resolution and managing the object lifecycle.
+
+
+
+h3(#object-ids). Object IDs
+
+Every object has a unique identifier that distinguishes it from all other objects.
+
+Object IDs follow a specific format:
+
+bc[text]. type:hash@timestamp
+
+For example:
+
+bc[text]. counter:J7x6mAF8X5Ha60VBZb6GtXSgnKJQagNLgadUlgICjkk@1734628392000
+
+This format has been specifically designed to ensure uniqueness in a globally distributed system and includes:
+
+* **type**: the object type (either @map@ or @counter@)
+* **hash**: a base64 string encoded hash derived from the initial value of the object and a random nonce
+* **timestamp**: a Unix millisecond timestamp denoting the creation time of the object
+
+
+
+h3(#tombstones). Tombstones
+
+Tombstones are markers indicating an object or map entry has been deleted.
+
+* A tombstone is created for an object when it becomes "unreachable":/docs/liveobjects/concepts/objects#reachability from the root object.
+* A tombstone is created for a map entry when it is "removed":/docs/liveobjects/map#remove
+
+Tombstones protect against lagging clients from re-introducing a deleted value, ensuring all clients eventually converge on the same state. They are eventually garbage collected after a safe period of time.
+
+h3(#timeserials). Timeserials
+
+When an operation message is published it is assigned a unique logical timestamp called a "timeserial".
+
+This timeserial is stored on map entries in order to implement last-write-wins conflict resolution semantics.
+
+Additionally, all objects store the timeserial of the last operation that was applied to the object. Since Ably operates fully independent data centers, these timeserials are stored on a per-site basis.
+
+Timeserial metadata is used for internal purposes and is not directly exposed in client libraries. However, it can be viewed using the "REST API":/docs/liveobjects/rest-api-usage .
diff --git a/content/liveobjects/concepts/operations.textile b/content/liveobjects/concepts/operations.textile
new file mode 100644
index 0000000000..a14f34d5d6
--- /dev/null
+++ b/content/liveobjects/concepts/operations.textile
@@ -0,0 +1,133 @@
+---
+title: Operations
+meta_description: "Learn how objects are updated by operations in Ably LiveObjects."
+product: liveobjects
+languages:
+ - javascript
+---
+
+
+
+LiveObjects operations define how object data is updated and synchronized across multiple clients. This document explains the key concepts you need to know when working with operations.
+
+h2(#operation-types). Operation Types
+
+Each object type supports specific operations that modify the object's data.
+
+h3(#livemap). LiveMap Operations
+
+"LiveMap":/docs/liveobjects/map supports the following operations:
+
+* @set@: Set a value for a key
+* @remove@: Remove a key and its value
+
+The value of an entry in a @LiveMap@ instance can be a "primitive type":/docs/liveobjects/concepts/objects#primitive-types or a "reference":/docs/liveobjects/concepts/objects#composability to another object.
+
+blang[javascript].
+
+ ```[javascript]
+ // Set a value for a key
+ await map.set('username', 'alice');
+
+ // Remove a key
+ await map.remove('username');
+ ```
+
+h3(#livecounter). LiveCounter Operations
+
+"LiveCounter":/docs/liveobjects/counter supports the following operations:
+
+* @increment@: Increment the counter by a specified amount
+* @decrement@: Decrement the counter by a specified amount
+
+The amount is a double-precision floating-point number, which is the same as underlying type of a "LiveCounter":/docs/liveobjects/concepts/objects#livecounter value.
+
+
+
+blang[javascript].
+
+ ```[javascript]
+ // Increment counter by 5
+ await counter.increment(5);
+
+ // Decrement counter by 2
+ await counter.decrement(2);
+ ```
+
+h3(#create-operations). Create Operations
+
+Create operations are used to instantiate new objects of a given type.
+
+A create operation can optionally specify an initial value for the object.
+
+blang[javascript].
+
+ ```[javascript]
+ // Create a map with initial values
+ const userMap = await channel.objects.createMap({
+ username: 'alice',
+ status: 'online'
+ });
+
+ // Create a counter with initial value
+ const scoreCounter = await channel.objects.createCounter(100);
+ ```
+
+When a create operation is processed, an "object ID":/docs/liveobjects/concepts/objects#object-ids for the new object instance is automatically generated for the object.
+
+
+
+h2(#object-ids). Object IDs
+
+Every operation is expressed relative to a specific object instance, identified by its "object ID":/docs/liveobjects/concepts/objects#object-ids , which determines which object the operation is applied to.
+
+When using a client library object IDs are handled automatically, allowing you work directly with object references:
+
+blang[javascript].
+
+ ```[javascript]
+ // The published operation targets the object ID of the `userMap` object instance
+ await userMap.set('username', 'alice');
+ ```
+
+Therefore it is important that you obtain an up-to-date object instance before performing operations on an object. For example, you can "subscribe":/docs/liveobjects/map#subscribe-data to a @LiveMap@ instance to ensure you always have an up-to-date reference to any child objects in the map:
+
+blang[javascript].
+
+ ```[javascript]
+ const root = await channel.objects.getRoot();
+
+ let myCounter = root.get('myCounter');
+ root.subscribe(() => { myCounter = root.get('myCounter'); });
+
+ // before incrementing, ensure we have an up-to-date object reference if
+ // the counter instance at the 'myCounter' key in the root map changes
+ await myCounter.increment(1);
+ ```
+
+In the "REST API":/docs/liveobjects/rest-api-usage#updating-objects-by-id , this relationship is made explicit:
+
+bc[sh]. curl -X POST https://rest.ably.io/channels/my-channel/objects \
+ -u "{{API_KEY}}"
+ -H "Content-Type: application/json" \
+ --data \
+'{
+ "operation": "MAP_SET",
+ "objectId": "root",
+ "data": {"key": "username", "value": {"string": "alice"}}
+}'
+
+h2(#batch-operations). Batch Operations
+
+"Batch operations":/docs/liveobjects/batch can be used to batch a set of operations together:
+
+* Multiple operations are grouped into a single atomic unit
+* All operations in the batch either succeed together or fail together
+* Operations in a batch are sent as a single message
+* No operations from other clients can be interleaved within a batch
diff --git a/content/liveobjects/concepts/synchronization.textile b/content/liveobjects/concepts/synchronization.textile
new file mode 100644
index 0000000000..fda9ef1a71
--- /dev/null
+++ b/content/liveobjects/concepts/synchronization.textile
@@ -0,0 +1,37 @@
+---
+title: Synchronization
+meta_description: "Learn how data is synchronized between clients."
+product: liveobjects
+languages:
+ - javascript
+---
+
+
+
+LiveObjects provides a powerful synchronization mechanism to ensure that all clients see the same data. This document explains how synchronization works in LiveObjects.
+
+h2(#channel-objects). Channel Objects
+
+Ably maintains the authoritative state of all objects on each channel across its distributed infrastructure.
+
+Each object instance is identified by a unique "object ID":/docs/liveobjects/concepts/objects#object-ids . The channel holds the complete up-to-date data of all objects on the channel.
+
+Ably stores the object data durably such that the data is available even after the channel becomes inactive. The data is stored in multiple regional datacenters and across multiple availability zones. This ensures that the data is available even if there is disruption in one or more datacenters.
+
+When a channel first becomes active in a region, the channel loads the object data from durable storage into memory to facilitate low-latency operation processing.
+
+h2(#client-objects). Client Objects
+
+While Ably maintains the source of truth on the channel, each connected client keeps a local representation of the objects on the channel.
+
+When the client first attaches to the channel, the state of the channel objects is streamed to the client. "Lifecycle events":/docs/liveobjects/lifecycle#synchronization allow your application to be notified when the local state is being synchronized with the Ably service.
+
+All object operations published to the channel are broadcast to subscribed clients, which apply the operations to their local client objects when they are received. This allows clients to maintain a consistent view of the channel objects in a bandwidth-efficient way, since only the operations (rather than the updated objects themselves) are sent over the client's connection.
+
+
+
+If there is a loss of continuity on the channel for any reason, such as the client becoming disconnected for more than two minutes and entering the "suspended state":/docs/connect/states#connection-states , the client objects will automatically be resynchronized when it reconnects.
diff --git a/content/liveobjects/counter.textile b/content/liveobjects/counter.textile
new file mode 100644
index 0000000000..3ea26208fb
--- /dev/null
+++ b/content/liveobjects/counter.textile
@@ -0,0 +1,190 @@
+---
+title: LiveCounter
+meta_description: "Create, update and receive updates for a numerical counter that synchronizes state across clients in realtime."
+product: liveobjects
+languages:
+ - javascript
+---
+
+
+
+LiveCounter is a synchronized numerical counter that supports increment and decrement operations. It ensures that all updates are correctly applied and synchronized across users in realtime, preventing inconsistencies when multiple users modify the counter value simultaneously.
+
+h2(#create). Create LiveCounter
+
+A @LiveCounter@ instance can be created using the @channel.objects.createCounter()@ method. It must be stored inside a @LiveMap@ object that is reachable from the "root object":/docs/liveobjects/concepts/objects#root-object .
+
+blang[javascript].
+
+ @channel.objects.createCounter()@ is asynchronous, as the client sends the create operation to the Ably system and waits for an acknowledgment of the successful counter creation.
+
+
+
+blang[javascript].
+
+ ```[javascript]
+ const counter = await channel.objects.createCounter();
+ await root.set('counter', counter);
+ ```
+
+Optionally, you can specify an initial value when creating the counter:
+
+blang[javascript].
+
+ ```[javascript]
+ const counter = await channel.objects.createCounter(100); // Counter starts at 100
+ ```
+
+h2(#value). Get counter value
+
+Get the current value of a counter using the @LiveCounter.value()@ method:
+
+blang[javascript].
+
+ ```[javascript]
+ console.log('Counter value:', counter.value());
+ ```
+
+h2(#subscribe-data). Subscribe to data updates
+
+You can subscribe to data updates on a counter to receive realtime changes made by you or other clients.
+
+
+
+Subscribe to data updates on a counter using the @LiveCounter.subscribe()@ method:
+
+blang[javascript].
+
+ ```[javascript]
+ counter.subscribe((update) => {
+ console.log('Counter updated:', counter.value());
+ console.log('Update details:', update);
+ });
+ ```
+
+The update object provides details about the change, such as the amount by which the counter value was changed.
+
+Example structure of an update object when the counter was incremented by 5:
+
+```[json]
+{
+ {
+ "amount": 5
+ }
+}
+```
+
+Or decremented by 10:
+
+```[json]
+{
+ {
+ "amount": -10
+ }
+}
+```
+
+h3(#unsubscribe-data). Unsubscribe from data updates
+
+Use the @unsubscribe()@ function returned in the @subscribe()@ response to remove a counter update listener:
+
+blang[javascript].
+
+ ```[javascript]
+ // Initial subscription
+ const { unsubscribe } = counter.subscribe(() => console.log(counter.value()));
+ // To remove the listener
+ unsubscribe();
+ ```
+
+Use the @LiveCounter.unsubscribe()@ method to deregister a provided listener:
+
+blang[javascript].
+
+ ```[javascript]
+ // Initial subscription
+ const listener = () => console.log(counter.value());
+ counter.subscribe(listener);
+ // To remove the listener
+ counter.unsubscribe(listener);
+ ```
+
+Use the @LiveCounter.unsubscribeAll()@ method to deregister all counter update listeners:
+
+blang[javascript].
+
+ ```[javascript]
+ counter.unsubscribeAll();
+ ```
+
+h2(#update). Update LiveCounter
+
+Update the counter value by calling @LiveCounter.increment()@ or @LiveCounter.decrement()@. These operations are synchronized across all clients and trigger data subscription callbacks for the counter, including on the client making the request.
+
+blang[javascript].
+
+ These operations are asynchronous, as the client sends the corresponding update operation to the Ably system and waits for acknowledgment of the successful counter update.
+
+blang[javascript].
+
+ ```[javascript]
+ await counter.increment(5); // Increase value by 5
+ await counter.decrement(2); // Decrease value by 2
+ ```
+
+h2(#subscribe-lifecycle). Subscribe to lifecycle events
+
+Subscribe to lifecycle events on a counter using the @LiveCounter.on()@ method:
+
+blang[javascript].
+
+ ```[javascript]
+ counter.on('deleted', () => {
+ console.log('Counter has been deleted');
+ });
+ ```
+
+Read more about "objects lifecycle events":/docs/liveobjects/lifecycle#objects.
+
+h3(#unsubscribe-lifecycle). Unsubscribe from lifecycle events
+
+Use the @off()@ function returned in the @on()@ response to remove a lifecycle event listener:
+
+blang[javascript].
+
+ ```[javascript]
+ // Initial subscription
+ const { off } = counter.on(('deleted') => console.log('Counter deleted'));
+ // To remove the listener
+ off();
+ ```
+
+Use the @LiveCounter.off()@ method to deregister a provided lifecycle event listener:
+
+blang[javascript].
+
+ ```[javascript]
+ // Initial subscription
+ const listener = () => console.log('Counter deleted');
+ counter.on('deleted', listener);
+ // To remove the listener
+ counter.off('deleted', listener);
+ ```
+
+Use the @LiveCounter.offAll()@ method to deregister all lifecycle event listeners:
+
+blang[javascript].
+
+ ```[javascript]
+ counter.offAll();
+ ```
diff --git a/content/liveobjects/inband-objects.textile b/content/liveobjects/inband-objects.textile
new file mode 100644
index 0000000000..35298e98ea
--- /dev/null
+++ b/content/liveobjects/inband-objects.textile
@@ -0,0 +1,72 @@
+---
+title: Inband Objects
+meta_description: "Subscribe to LiveObjects updates from Pub/Sub SDKs."
+product: liveobjects
+languages:
+ - javascript
+---
+
+
+
+Inband objects enables clients to subscribe to LiveObjects updates in realtime, even on platforms that don't yet have a native LiveObjects Realtime client implementation.
+
+
+
+Inband objects works by delivering changes to channel objects as regular channel messages, similar to "inband occupancy":/docs/channels/options#occupancy .
+
+h2(#inband-objects-enable). Enable Inband Objects
+
+To enable inband objects, use the @objects@ "channel parameter":/docs/channels/options#objects when getting a channel:
+
+blang[javascript].
+
+ ```[javascript]
+ // When getting a channel instance
+ const channelOpts = { params: { objects: 'objects' } };
+ const channel = realtime.channels.get('my-channel', channelOpts);
+
+ // Or using setOptions on an existing channel
+ await channel.setOptions({ params: { objects: 'objects' } });
+ ```
+
+
+
+h2(#inband-objects-subscribe). Subscribe to updates
+
+When using inband objects, the client will receive special @[meta]objects@ messages whenever the objects on the channel are updated. These messages provide a snapshot of the current set of objects on the channel.
+
+
+
+"Subscribe":/docs/api/realtime-sdk/channels#subscribe to @[meta]objects@ messages like you would any other message on the channel. For convenience, use a message name filter to only receive messages with the name @[meta]objects@ in your listener:
+
+blang[javascript].
+
+ ```[javascript]
+ // Subscribe to [meta]objects messages
+ channel.subscribe('[meta]objects', (message) => {
+ const { syncId, nextCursor, object } = message.data;
+ console.log("Received inband objects message:", syncId, nextCursor, JSON.stringify(message.data));
+ });
+ ```
+
+h2(#inband-objects-message-format). Message Format
+
+Inband objects messages are sent as a sequence of messages, where each message contains a snapshot of a single object on the channel. Taken together, a set of messages belonging to the same sequence describes the complete set of objects on the channel.
+
+Each inband objects message has a message @name@ of @[meta]objects@.
+
+The message @data@ is a JSON object with the following top-level properties:
+
+* @syncId@: A unique ID for this sequence. All messages with the same @syncId@ are part of the same sequence of messages which describes the complete set of the objects on the channel.
+* @nextCursor@: A cursor for the next message in the sequence, or @undefined@ if this is the last message in the sequence.
+* @object@: A JSON representation of the object included in the message.
+
+The shape of the @object@ is the same as the response format of an object when listing them via the "REST API":/docs/liveobjects/rest-api-usage?lang=javascript#fetching-objects-list-values .
diff --git a/content/liveobjects/index.textile b/content/liveobjects/index.textile
new file mode 100644
index 0000000000..cd62cb0271
--- /dev/null
+++ b/content/liveobjects/index.textile
@@ -0,0 +1,68 @@
+---
+title: About LiveObjects
+meta_description: "Learn about Ably LiveObjects, its features, use cases, and how it simplifies realtime state synchronization."
+product: liveobjects
+---
+
+
+
+Ably LiveObjects enables effortless realtime synchronization of application state across multiple users and devices at any scale. LiveObjects provides a set of purpose-built APIs and data structures to handle the complexities of persisting and synchronizing state, freeing you to focus on building features instead of managing concurrency or conflict resolution.
+
+LiveObjects enables you to store data as "objects" on a channel. These objects are automatically synchronized in realtime across all connected clients, and any conflicts that arise from concurrent updates are seamlessly resolved in the background.
+
+LiveObjects is managed and persisted on a per-channel basis and benefit from the same performance guarantees and scaling potential as "channels":/docs/channels.
+
+The LiveObjects API is available as a feature of "channels":/docs/channels within the Ably Pub/Sub SDK and can be accessed via the "@channel.objects@":/docs/api/realtime-sdk/channels#objects property.
+
+h2(#use-cases). Use cases
+
+Ably LiveObjects is useful when your application has data that:
+
+* Is shared by many users
+* Needs to be synchronized in realtime
+* Can be updated concurrently by many users
+
+Use Ably LiveObjects to build scalable realtime applications such as:
+
+* Voting and polling systems: Platforms that need the ability to count and display votes in realtime, such as audience engagement tools, quizzes, and decision-making applications.
+* Collaborative applications: Tools like shared whiteboards or content and product management applications where multiple users edit shared content simultaneously.
+* Live leaderboards: Multiplayer games or competition-based applications that require up-to-date rankings and scoreboards.
+* Game state: Applications that present dynamic in-game statistics or game state in realtime, such as player health, scores, and inventory changes.
+* Shared configuration, settings or controls: Systems where configuration parameters are shared or updated across multiple users or devices.
+
+h2(#features). LiveObjects features
+
+Ably LiveObjects provides the following key features:
+
+* "LiveCounter":#counter
+* "LiveMap":#map
+* "Composability":#composability
+* "Batching operations":#batch
+
+h3(#counter). LiveCounter
+
+"LiveCounter":/docs/liveobjects/counter is a numerical counter that supports increment and decrement operations. It ensures that all updates are correctly applied and synchronized across users in realtime, preventing inconsistencies when multiple users modify the counter value simultaneously.
+
+LiveCounter is ideal for scenarios such as:
+
+* Tracking reactions (likes, upvotes, or downvotes) in social applications.
+* Counting active users in a chatroom.
+* Maintaining live leaderboard scores in competitive applications.
+
+h3(#map). LiveMap
+
+"LiveMap":/docs/liveobjects/map is a key/value data structure that synchronizes its state across users in realtime. It enables you to store primitive values, such as numbers, strings, booleans and buffers, as well as other objects, enabling "composable data structures":#composability.
+
+Conflicts in a LiveMap are automatically resolved with last-write-wins (LWW) semantics.
+
+h3(#composability). Composability
+
+A "LiveMap":/docs/liveobjects/map#composability can store references to other @LiveMap@ or @LiveCounter@ objects as values for its keys, enabling you to build complex, hierarchical object structure. This enables you to represent complex data models in your applications while ensuring realtime synchronization across clients.
+
+h3(#batch). Batch operations
+
+"Batching":/docs/liveobjects/batch enables multiple LiveObjects operations to be grouped into a single channel message, ensuring atomic application of grouped operations. This prevents partial updates of your data and ensures consistency across all users.
+
+Batching is particularly useful in scenarios where multiple dependent updates need to be processed together, ensuring a seamless experience for users.
diff --git a/content/liveobjects/lifecycle.textile b/content/liveobjects/lifecycle.textile
new file mode 100644
index 0000000000..36f1722971
--- /dev/null
+++ b/content/liveobjects/lifecycle.textile
@@ -0,0 +1,70 @@
+---
+title: Lifecycle events
+meta_description: "Understand lifecycle events for Objects, LiveMap and LiveCounter to track synchronization events and object deletions."
+product: liveobjects
+languages:
+ - javascript
+---
+
+
+
+h2(#synchronization). Objects synchronization events
+
+The "@channel.objects@":/docs/api/realtime-sdk/channels#objects instance emits synchronization events that indicate when the local state on the client is being synchronized with the Ably service. These events can be useful for displaying loading indicators, preventing user interactions during synchronization, or triggering application logic when data is first loaded.
+
+* @syncing@ - Emitted when the local copy of objects on a channel begins synchronizing with the Ably service.
+* @synced@ - Emitted when the local copy of objects on a channel has been synchronized with the Ably service.
+
+blang[javascript].
+
+ ```[javascript]
+ channel.objects.on('syncing', () => {
+ console.log('Objects are syncing...');
+ // Show a loading indicator and disable edits in the application
+ });
+
+ channel.objects.on('synced', () => {
+ console.log('Objects have been synced.');
+ // Hide loading indicator
+ });
+ ```
+
+
+
+h2(#objects-lifecycle). LiveMap/LiveCounter lifecycle events
+
+Lifecycle events enable you to monitor changes in an object's lifecycle.
+
+Currently, only the @deleted@ event can be emitted. Understanding the conditions under which this event is emitted and handling it properly ensures that your application maintains expected behavior.
+
+h3(#objects-deleted). deleted event
+
+Objects that were created on a channel can become orphaned when they were never assigned to the object tree, or because their reference was removed using "@LiveMap.remove()@":/docs/liveobjects/map#remove and never reassigned. Orphaned objects will be garbage collected by Ably, typically after 24 hours. When this happens, a @deleted@ event is broadcast for the affected object. Once deleted, an object can no longer be interacted with, and any operations performed on it will result in an error.
+
+While the LiveObjects feature internally manages object deletions and removes them from its internal state, your application may still hold references to these deleted objects in separate data structures. The @deleted@ event provides a way to react accordingly by removing references to deleted objects and preventing potential errors.
+
+In most cases, subscribing to @deleted@ events is unnecessary. Your application should have already reacted to object removal when a corresponding "@LiveMap.remove()@":/docs/liveobjects/map#remove operation was received. However, if your application separately stores references to object instances and does not properly clear them when objects are orphaned, any later interactions with those objects after they are deleted will result in an error. In such cases, subscribing to @deleted@ events helps ensure that those references are cleaned up and runtime errors are avoided.
+
+
+
+blang[javascript].
+
+ ```[javascript]
+ const { off } = counter.on('deleted', () => {
+ console.log('LiveCounter has been deleted.');
+ // Remove references to this object from your application
+ // as it can no longer be interacted with
+ });
+ ```
+
+Read more about subscribing to object lifecycle events for "LiveCounter":/docs/liveobjects/counter#subscribe-lifecycle and "LiveMap":/docs/liveobjects/map#subscribe-lifecycle.
diff --git a/content/liveobjects/map.textile b/content/liveobjects/map.textile
new file mode 100644
index 0000000000..f6ae33c037
--- /dev/null
+++ b/content/liveobjects/map.textile
@@ -0,0 +1,250 @@
+---
+title: LiveMap
+meta_description: "Create, update and receive updates for a key/value data structure that synchronizes state across clients in realtime."
+product: liveobjects
+languages:
+ - javascript
+---
+
+
+
+LiveMap is a key/value data structure that synchronizes its state across users in realtime. It enables you to store primitive values, such as numbers, strings, booleans and buffers, as well as other objects, "enabling you to build complex, hierarchical object structure":#composability.
+
+Conflicts in a LiveMap are automatically resolved with last-write-wins (LWW) semantics. The latest received operation on a key will be applied to the LiveMap and broadcast to all clients.
+
+h2(#create). Create LiveMap
+
+A @LiveMap@ instance can be created using the @channel.objects.createMap()@ method. It must be stored inside another @LiveMap@ object that is reachable from the "root object":/docs/liveobjects/concepts/objects#root-object .
+
+blang[javascript].
+
+ @channel.objects.createMap()@ is asynchronous, as the client sends the create operation to the Ably system and waits for an acknowledgment of the successful map creation.
+
+
+
+blang[javascript].
+
+ ```[javascript]
+ const map = await channel.objects.createMap();
+ await root.set('myMap', map);
+ ```
+
+Optionally, you can specify an initial key/value structure when creating the map:
+
+blang[javascript].
+
+ ```[javascript]
+ // Pass a regular JavaScript object reflecting the initial state
+ const map = await channel.objects.createMap({ foo: 'bar', baz: 42 });
+ // You can also pass other objects as values for keys
+ await channel.objects.createMap({ nestedMap: map });
+ ```
+
+h2(#get). Get value for a key
+
+Get the current value for a key in a map using the @LiveMap.get()@ method:
+
+blang[javascript].
+
+ ```[javascript]
+ console.log('Value for my-key:', map.get('my-key'));
+ ```
+
+h2(#subscribe-data). Subscribe to data updates
+
+You can subscribe to data updates on a map to receive realtime changes made by you or other clients.
+
+
+
+Subscribe to data updates on a map using the @LiveMap.subscribe()@ method:
+
+blang[javascript].
+
+ ```[javascript]
+ map.subscribe((update) => {
+ console.log('Map updated:', [...map.entries()]);
+ console.log('Update details:', update);
+ });
+ ```
+
+The update object provides details about the change, listing the keys that were changed and indicating whether they were updated (value changed) or removed from the map.
+
+Example structure of an update object when the key @foo@ is updated and the key @bar@ is removed:
+
+```[json]
+{
+ {
+ "foo": "updated",
+ "bar": "removed"
+ }
+}
+```
+
+h3(#unsubscribe-data). Unsubscribe from data updates
+
+Use the @unsubscribe()@ function returned in the @subscribe()@ response to remove a map update listener:
+
+blang[javascript].
+
+ ```[javascript]
+ // Initial subscription
+ const { unsubscribe } = map.subscribe(() => console.log('Map updated'));
+ // To remove the listener
+ unsubscribe();
+ ```
+
+Use the @LiveMap.unsubscribe()@ method to deregister a provided listener:
+
+blang[javascript].
+
+ ```[javascript]
+ // Initial subscription
+ const listener = () => console.log('Map updated');
+ map.subscribe(listener);
+ // To remove the listener
+ map.unsubscribe(listener);
+ ```
+
+Use the @LiveMap.unsubscribeAll()@ method to deregister all map update listeners:
+
+blang[javascript].
+
+ ```[javascript]
+ map.unsubscribeAll();
+ ```
+
+h2(#set). Set keys in a LiveMap
+
+Set a value for a key in a map by calling @LiveMap.set()@. This operation is synchronized across all clients and triggers data subscription callbacks for the map, including on the client making the request.
+
+Keys in a map can contain numbers, strings, booleans and buffers, as well as other @LiveMap@ and @LiveCounter@ objects.
+
+blang[javascript].
+
+ This operation is asynchronous, as the client sends the corresponding update operation to the Ably system and waits for acknowledgment of the successful map key update.
+
+blang[javascript].
+
+ ```[javascript]
+ await map.set('foo', 'bar');
+ await map.set('baz', 42);
+
+ // Can also set a reference to another object
+ const counter = await channel.objects.createCounter();
+ await map.set('counter', counter);
+ ```
+
+h2(#remove). Remove a key from a LiveMap
+
+Remove a key from a map by calling @LiveMap.remove()@. This operation is synchronized across all clients and triggers data subscription callbacks for the map, including on the client making the request.
+
+blang[javascript].
+
+ This operation is asynchronous, as the client sends the corresponding remove operation to the Ably system and waits for acknowledgment of the successful map key removal.
+
+blang[javascript].
+
+ ```[javascript]
+ await map.remove('foo');
+ ```
+
+h2(#iterate). Iterate over key/value pairs
+
+blang[javascript].
+
+ Iterate over key/value pairs, keys or values using the @LiveMap.entries()@, @LiveMap.keys()@ and @LiveMap.values()@ methods respectively.
+
+ These methods return a "map iterator":https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator object for convenient traversal. Note that contrary to JavaScript's "Map":https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map counterpart, these methods do not guarantee that entries are returned in insertion order.
+
+blang[javascript].
+
+ ```[javascript]
+ for (const [key, value] of map.entries()) {
+ console.log(`Key: ${key}, Value: ${value}`);
+ }
+
+ for (const key of map.keys()) {
+ console.log(`Key: ${key}`);
+ }
+
+ for (const value of map.values()) {
+ console.log(`Value: ${value}`);
+ }
+ ```
+
+h2(#subscribe-lifecycle). Subscribe to lifecycle events
+
+Subscribe to lifecycle events on a map using the @LiveMap.on()@ method:
+
+blang[javascript].
+
+ ```[javascript]
+ map.on('deleted', () => {
+ console.log('Map has been deleted');
+ });
+ ```
+
+Read more about "objects lifecycle events":/docs/liveobjects/lifecycle#objects.
+
+h3(#unsubscribe-lifecycle). Unsubscribe from lifecycle events
+
+Use the @off()@ function returned in the @on()@ response to remove a lifecycle event listener:
+
+blang[javascript].
+
+ ```[javascript]
+ // Initial subscription
+ const { off } = map.on(('deleted') => console.log('Map deleted'));
+ // To remove the listener
+ off();
+ ```
+
+Use the @LiveMap.off()@ method to deregister a provided lifecycle event listener:
+
+blang[javascript].
+
+ ```[javascript]
+ // Initial subscription
+ const listener = () => console.log('Map deleted');
+ map.on('deleted', listener);
+ // To remove the listener
+ map.off('deleted', listener);
+ ```
+
+Use the @LiveMap.offAll()@ method to deregister all lifecycle event listeners:
+
+blang[javascript].
+
+ ```[javascript]
+ map.offAll();
+ ```
+
+h2(#composability). Composability
+
+A @LiveMap@ can store other @LiveMap@ or @LiveCounter@ objects as values for its keys, enabling you to build complex, hierarchical object structure. This enables you to represent complex data models in your applications while ensuring realtime synchronization across clients.
+
+blang[javascript].
+
+ ```[javascript]
+ // Create a hierarchy of objects using LiveMap
+ const counter = await channel.objects.createCounter();
+ const map = await channel.objects.createMap({ nestedCounter: counter });
+ const outerMap = await channel.objects.createMap({ nestedMap: map });
+ await root.set('outerMap', outerMap);
+
+ // resulting structure:
+ // root (LiveMap)
+ // └── outerMap (LiveMap)
+ // └── nestedMap (LiveMap)
+ // └── nestedCounter (LiveCounter)
+ ```
diff --git a/content/liveobjects/quickstart.textile b/content/liveobjects/quickstart.textile
new file mode 100644
index 0000000000..57b8b66660
--- /dev/null
+++ b/content/liveobjects/quickstart.textile
@@ -0,0 +1,203 @@
+---
+title: Quickstart
+meta_description: "A quickstart guide to learn the basics of integrating the Ably LiveObjects product into your application."
+product: liveobjects
+languages:
+ - javascript
+---
+
+
+
+This guide shows how to integrate Ably LiveObjects into your application.
+
+You will learn how to:
+
+* Create an Ably account and get an API key for authentication.
+* Install the Ably Pub/Sub SDK.
+* Create a channel with LiveObjects functionality enabled.
+* Create, update and subscribe to changes on LiveObjects data structures: "LiveMap":/docs/liveobjects/map and "LiveCounter":/docs/liveobjects/counter.
+
+h2(#step-0). Authentication
+
+An "API key":/docs/auth#api-keys is required to authenticate with Ably. API keys are used either to authenticate directly with Ably using "basic authentication":/docs/auth/basic, or to generate tokens for untrusted clients using "token authentication":/docs/auth/token.
+
+
+
+"Sign up":https://ably.com/sign-up for a free account and create your own API key in the "dashboard":https://ably.com/dashboard or use the "Control API":/docs/account/control-api to create an API key programmatically.
+
+API keys and tokens have a set of "capabilities":/docs/auth/capabilities assigned to them that specify which operations can be performed on which resources. The following capabilities are available for LiveObjects:
+
+* @object-subscribe@ - grants clients read access to LiveObjects, allowing them to get the root object and subscribe to updates.
+* @object-publish@ - grants clients write access to LiveObjects, allowing them to perform mutation operations on objects.
+
+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 calling mutation methods on LiveObjects.
+
+For the purposes of this guide, make sure your API key includes both @object-subscribe@ and @object-publish@ "capabilities":/docs/auth/capabilities to allow full read and write access.
+
+h2(#step-1). Install Ably Pub/Sub SDK
+
+blang[javascript].
+
+ LiveObjects is available as part of the Ably Pub/Sub SDK via the dedicated Objects plugin.
+
+blang[javascript].
+
+ h3(#npm). NPM
+
+ Install the Ably Pub/Sub SDK as an "NPM module":https://www.npmjs.com/package/ably:
+
+ ```[sh]
+ npm install ably
+ ```
+
+ Import the SDK and the Objects plugin into your project:
+
+ ```[javascript]
+ import * as Ably from 'ably';
+ import Objects from 'ably/objects';
+ ```
+
+blang[javascript].
+
+ h3(#cdn). CDN
+
+ Reference the Ably Pub/Sub SDK and the Objects plugin within your HTML file:
+
+ ```[html]
+
+
+
+ ```
+
+h2(#step-2). Instantiate a client
+
+blang[javascript].
+
+ Instantiate an Ably Realtime client from the Pub/Sub SDK, providing the Objects plugin:
+
+ ```[javascript]
+ const realtimeClient = new Ably.Realtime({ key: '{{API_KEY}}', plugins: { Objects } });
+ ```
+
+blang[javascript].
+
+ A "@ClientOptions@":/docs/api/realtime-sdk#client-options object may be passed to the Pub/Sub SDK instance to further customize the connection, however at a minimum you must set an API key and provide an @Objects@ plugin so that the client can use LiveObjects functionality.
+
+h2(#step-3). Create a channel
+
+LiveObjects is managed and persisted on "channels":/docs/channels. To use LiveObjects, you must first create a channel with the correct "channel mode flags":/docs/channels/options#modes :
+
+* @OBJECT_SUBSCRIBE@ - required to access objects on a channel.
+* @OBJECT_PUBLISH@ - required to create and modify objects on a channel.
+
+
+
+blang[javascript].
+
+ ```[javascript]
+ const channelOptions = { modes: ['OBJECT_SUBSCRIBE', 'OBJECT_PUBLISH'] };
+ const channel = realtimeClient.channels.get('my_liveobjects_channel', channelOptions);
+ ```
+
+Next, you need to "attach to the channel":/docs/channels/states. Attaching to a channel starts an initial synchronization sequence where the objects on the channel are sent to the client.
+
+blang[javascript].
+
+ ```[javascript]
+ await channel.attach();
+ ```
+
+h2(#step-4). Get root object
+
+The "@channel.objects@":/docs/api/realtime-sdk/channels#objects property gives access to the LiveObjects API for a channel.
+
+Use it to get the root object, which is the entry point for accessing and persisting objects on a channel. The root object is a "@LiveMap@":/docs/liveobjects/map instance that always exists on a channel and acts as the top-level node in your object tree. You can get the root object using the @getRoot()@ function of LiveObjects:
+
+blang[javascript].
+
+ ```[javascript]
+ // The promise resolves once the LiveObjects state is synchronized with the Ably system
+ const root = await channel.objects.getRoot();
+ ```
+
+h2(#step-5). Create objects
+
+You can create new objects using dedicated functions of the LiveObjects API at "@channel.objects@":/docs/api/realtime-sdk/channels#objects. To persist them on a channel and share them between clients, you must assign objects to a parent @LiveMap@ instance connected to the root object. The root object itself is a @LiveMap@ instance, so you can assign objects to the root and start building your object tree from there.
+
+
+
+blang[javascript].
+
+ ```[javascript]
+ const visitsCounter = await channel.objects.createCounter();
+ const reactionsMap = await channel.objects.createMap();
+
+ await root.set('visits', visitsCounter);
+ await root.set('reactions', reactionsMap);
+ ```
+
+h2(#step-6). Subscribe to updates
+
+Subscribe to realtime updates to objects on a channel. You will be notified when an object is updated by other clients or by you.
+
+blang[javascript].
+
+ ```[javascript]
+ visitsCounter.subscribe(() => {
+ console.log('Visits counter updated:', visitsCounter.value());
+ });
+
+ reactionsMap.subscribe(() => {
+ console.log('Reactions map updated:', [...reactionsMap.entries()]);
+ });
+ ```
+
+h2(#step-7). Update objects
+
+Update objects using mutation methods. All subscribers (including you) will be notified of the changes when you update an object:
+
+blang[javascript].
+
+ ```[javascript]
+ await visitsCounter.increment(5);
+ // console: "Visits counter updated: 5"
+ await visitsCounter.decrement(2);
+ // console: "Visits counter updated: 2"
+
+ await reactionsMap.set('like', 10);
+ // console: "Reactions map updated: [['like',10]]"
+ await reactionsMap.set('love', 10);
+ // console: "Reactions map updated: [['like',10],['love',5]]"
+ await reactionsMap.remove('like');
+ // console: "Reactions map updated: [['love',5]]"
+ ```
+
+
+
+h2(#step-8). Next steps
+
+This quickstart introduced the basic concepts of LiveObjects and demonstrated how it works. The next steps are to:
+
+* Read more about "LiveCounter":/docs/liveobjects/counter and "LiveMap":/docs/liveobjects/map.
+* Learn about "Batching Operations":/docs/liveobjects/batch.
+* Learn about "Objects Lifecycle Events":/docs/liveobjects/lifecycle.
+* Add "Typings":/docs/liveobjects/typing for your LiveObjects.
diff --git a/content/liveobjects/rest-api-usage.textile b/content/liveobjects/rest-api-usage.textile
new file mode 100644
index 0000000000..45bc8a99e9
--- /dev/null
+++ b/content/liveobjects/rest-api-usage.textile
@@ -0,0 +1,929 @@
+---
+title: Using the REST API
+meta_description: "Learn how to work with Ably LiveObjects using the REST API"
+product: liveobjects
+---
+
+
+
+LiveObjects provides a comprehensive REST API that allows you to directly work with objects without using a client SDK.
+
+h2(#authentication). Authentication
+
+View the REST API "authentication":/docs/api/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":/docs/auth/capabilities to allow full read and write access.
+
+h2(#data-values). Data values
+
+When working with objects via the REST API, "primitive types":/docs/liveobjects/concepts/objects#primitive-types and "object references":/docs/liveobjects/concepts/objects#composability are included in request and response bodies under @data@ fields.
+
+The key in the @data@ object indicates the type of the value:
+
+```[json]
+{ "data": { "number" : 42 }}
+{ "data": { "string" : "LiveObjects is awesome" }}
+{ "data": { "boolean" : true }}
+{ "data": { "bytes": "TGl2ZU9iamVjdHMgaXMgYXdlc29tZQo=" }}
+{ "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" }}
+```
+
+
+
+h2(#fetching-objects). Fetching objects
+
+h3(#fetching-objects-list). List objects
+
+h6. GET rest.ably.io/channels/@@/objects
+
+Fetch a flat list of objects on the channel:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+The response includes the IDs of the objects on the channel:
+
+```[json]
+[
+ "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669"
+ "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
+ "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519",
+ "root",
+]
+```
+
+h4(#fetching-objects-list-values). Including values
+
+To include values of the objects in the response, set the @values=true@ query parameter:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects?values=true" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+```[json]
+[
+ {
+ "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"
+ }
+ }
+ }
+ }
+ }
+]
+```
+
+h4(#fetching-objects-list-metadata). Including metadata
+
+You can optionally include additional object "metadata":/docs/liveobjects/concepts/objects#metadata with the @metadata@ query option:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects?values=true&metadata=true" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+```[json]
+[
+ {
+ "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
+ }
+]
+```
+
+
+
+h4(#fetching-objects-list-pagination). Pagination
+
+The response can be "paginated":/docs/api/rest-api#pagination 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:
+
+```[sh]
+ curl -v -X GET "https://rest.ably.io/channels/my-channel/objects?values=true&limit=2" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+```[json]
+[
+ {
+ "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669",
+ "counter": {
+ "data": {
+ "number": 10
+ }
+ }
+ },
+ {
+ "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
+ "counter": {
+ "data": {
+ "number": 5
+ }
+ }
+ }
+]
+```
+
+The response includes @Link@ headers which provide relative links to the first, current and next pages of the response:
+
+```
+link: ; rel="first"
+link: ; rel="current"
+link: ; rel="next"
+```
+
+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:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects?cursor=map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519&limit=2&values=true" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+```[json]
+[
+ {
+ "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"
+ }
+ }
+ }
+ }
+ }
+]
+```
+
+h3(#fetching-objects-get). Get objects
+
+h6. GET rest.ably.io/channels/@@/objects/@@
+
+h4(#fetching-objects-get-single). Get a single object
+
+To fetch a single object on the channel, specify the object ID in the URL path:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects/root" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+The response contains a single object referencing any nested child objects by their object ID:
+
+```[json]
+{
+ "objectId": "root",
+ "map": {
+ "entries": {
+ "votes": {
+ "data": { "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519" }
+ }
+ }
+ }
+}
+```
+
+h4(#fetching-objects-get-children). Get an object and its children
+
+To fetch the objects on the channel in a tree structure use the @children@ query parameter:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+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:
+
+```[json]
+{
+ "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
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+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.
+
+h4(#fetching-objects-get-metadata). Including metadata
+
+You can optionally include additional object "metadata":/docs/liveobjects/concepts/objects#metadata for all objects included in the response with the @metadata@ query option:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true&metadata=true" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+```[json]
+{
+ "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
+}
+```
+
+h4(#fetching-objects-get-pagination). Pagination
+
+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:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true&limit=1" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+```[json]
+{
+ "objectId": "root",
+ "map": {
+ "entries": {
+ "votes": {
+ "data": {
+ "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519"
+ }
+ }
+ }
+ }
+}
+```
+
+To obtain the next page, make a subsequent query specifying the referenced object ID as the entrypoint:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects/map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519?children=true&limit=1" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+```[json]
+{
+ "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519",
+ "map": {
+ "entries": {
+ "down": {
+ "data": {
+ "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669"
+ }
+ },
+ "up": {
+ "data": {
+ "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269"
+ }
+ }
+ }
+ }
+}
+```
+
+h4(#fetching-objects-get-cycles). Cyclic references
+
+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:
+
+```[sh]
+ curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -H "Content-Type: application/json" \
+ -d '{
+ "operation": "MAP_SET",
+ "objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519",
+ "data": {"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 root object:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+```[json]
+{
+ "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"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+h3(#fetching-objects-compact). Get a compact view of objects
+
+h6. GET rest.ably.io/channels/@@/objects/@@/compact
+
+To fetch the objects on the channel in a tree structure in a concise format:
+
+```[sh]
+ curl -X GET "https://rest.ably.io/channels/my-channel/objects/root/compact" \
+ -u {{API_KEY}}
+ -H "Content-Type: application/json"
+```
+
+The response includes a compact representation of the object tree that is easy to read:
+
+```[json]
+{
+ "votes": {
+ "up": 5,
+ "down": 10
+ }
+}
+```
+
+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":#fetching-objects-get-cycles 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:
+
+```[json]
+{
+ "votes": {
+ "up": 5,
+ "down": 10,
+ "myRoot": { "objectId": "root" }
+ }
+}
+```
+
+The compact format inlines object ID references under the @objectId@ key, allowing references to other objects to be differentiated from string values.
+
+
+
+h2(#updating-objects). Publishing operations
+
+h6. POST rest.ably.io/channels/@@/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:
+
+```[json]
+{
+ "operation": "",
+ "objectId": "",
+ "path": "",
+ "data": ""
+}
+```
+
+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.
+
+h3(#updating-objects-by-id). Update a specific object instance
+
+To perform operations on a specific object instance, you need to provide its object ID in the request body:
+
+```[sh]
+ curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -H "Content-Type: application/json" \
+ -d '{
+ "operation": "COUNTER_INC",
+ "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
+ "data": {"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]
+{
+ "messageId": "TJPWHhMTrF:0",
+ "channel": "my-channel",
+ "objectIds": ["counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269"]
+}
+```
+
+h3(#updating-objects-by-path). Update an object by path
+
+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":#fetching-objects-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:
+
+```[sh]
+ curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -H "Content-Type: application/json" \
+ -d '{
+ "operation": "COUNTER_INC",
+ "path": "votes.up",
+ "data": { "number": 1 }
+ }'
+```
+
+
+
+You can use wildcards in paths to target multiple objects at once. To increment all @LiveCounter@ instances in the @votes@ @LiveMap@ instance:
+
+```[sh]
+ curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -H "Content-Type: application/json" \
+ -d '{
+ "operation": "COUNTER_INC",
+ "path": "votes.*",
+ "data": { "number": 1 }
+ }'
+```
+
+The response includes the IDs of each of the affected object instances:
+
+```[json]
+{
+ "messageId": "0Q1w-LpA11:0",
+ "channel": "my-channel",
+ "objectIds": [
+ "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
+ "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669"
+ ]
+}
+```
+
+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:
+
+```[json]
+{
+ "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:
+
+```[sh]
+ curl -X POST "https://sandbox-rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -H "Content-Type: application/json" \
+ -d '{
+ "operation": "COUNTER_INC",
+ "path": "posts.*.votes.up",
+ "data": { "number": 1 }
+ }'
+```
+
+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@:
+
+```[sh]
+ curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -H "Content-Type: application/json" \
+ -d '{
+ "operation": "COUNTER_INC",
+ "path": "posts.post\.123.votes.up",
+ "data": { "number": 1 }
+ }'
+```
+
+h3(#creating-objects). Creating objects
+
+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:
+
+```[json]
+{
+ "operation": "MAP_CREATE",
+ "data": {
+ "title": {"string": "LiveObjects is awesome"},
+ "createdAt": {"number": 1745835181122},
+ "isPublished": {"boolean": true}
+ }
+}
+```
+
+For @COUNTER_CREATE@, the @data@ field should be a JSON object that contains the initial value for the @LiveCounter@ instance:
+
+```[json]
+{
+ "operation": "COUNTER_CREATE",
+ "data": { "number": 5 }
+}
+```
+
+When you create a new object it is important that the new object is assigned to the object tree so that it is "reachable":/docs/liveobjects/concepts/objects#reachability 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@:
+
+```[sh]
+ curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -H "Content-Type: application/json" \
+ -d '{
+ "operation": "MAP_CREATE",
+ "path": "posts.post1",
+ "data": {
+ "title": {"string": "LiveObjects is awesome"},
+ "createdAt": {"number": 1745835181122},
+ "isPublished": {"boolean": true}
+ }
+ }'
+```
+
+When using the @path@ specifier with a @COUNTER_CREATE@ or @MAP_CREATE@ operation, the server constructs _two_ operations which are published as a "batch":#batch-operations :
+
+* 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:
+
+```[json]
+{
+ "messageId": "mkfjWU2jju:0",
+ "channel": "my-channel",
+ "objectIds": [
+ "map:cRCKx-eev7Tl66jGfl1SkZh_uEMo6F5jyV0B7mUn4Zs@1745835549101",
+ "map:a_oQqPYUGxi95_Cn0pWcsoeBlHZZtVW5xKIw0hnJCZs@1745835547258"
+ ]
+}
+```
+
+h3(#removing-objects). Removing objects
+
+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:
+
+```[sh]
+ curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -H "Content-Type: application/json" \
+ -d '{
+ "operation": "MAP_REMOVE",
+ "objectId": "root",
+ "data": {"key": "posts"}
+ }'
+```
+
+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.
+
+
+
+h3(#batch-operations). Batch operations
+
+You can group several operations into a single request by sending an array of operations.
+
+All operations inside the array form a "batch operation":/docs/liveobjects/concepts/operations#batch-operations 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:
+
+```[sh]
+ curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -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}
+ }
+ ]'
+```
+
+h3(#idempotent-operations). Idempotent operations
+
+Publish operations idempotently in the same way as for "idempotent message publishing":/docs/api/rest-api#idempotent-publish by specifying a @id@ for the operation message:
+
+```[sh]
+ curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -H "Content-Type: application/json" \
+ -d '{
+ "id": "my-idempotency-key",
+ "operation": "COUNTER_INC",
+ "objectId": "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
+ "data": {"number": 1}
+ }'
+```
+
+For batch operations, use the format @:@ where the index is the zero-based index of the operation in the array:
+
+```[sh]
+ curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
+ -u {{API_KEY}} \
+ -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}
+ }
+ ]'
+```
diff --git a/content/liveobjects/typing.textile b/content/liveobjects/typing.textile
new file mode 100644
index 0000000000..cfe1e6934b
--- /dev/null
+++ b/content/liveobjects/typing.textile
@@ -0,0 +1,91 @@
+---
+title: Typing
+meta_description: "Type objects on a channel for type safety and code autocompletion."
+product: liveobjects
+languages:
+ - javascript
+---
+
+
+
+blang[javascript].
+
+ If you are using TypeScript in your project, you can leverage LiveObjects' built-in TypeScript support to ensure type safety and enable autocompletion when working with objects on a channel.
+
+ h2(#global). Global ObjectsTypes interface
+
+ You can type objects on all your channels by defining a global @ObjectsTypes@ interface. If you only want to type the root object for a specific channel, see the "Typing channel.objects.getRoot()":#getroot section below.
+
+ Define the @ObjectsTypes@ interface in a type declaration file. You can create a file named @ably.config.d.ts@ in the root of your application:
+
+blang[javascript].
+
+ ```[javascript]
+ // file: ably.config.d.ts
+ import { LiveCounter, LiveMap } from 'ably';
+
+ // Define dedicated types and export them for reuse in your application
+ export type MyCustomRoot = {
+ reactions: LiveMap<{
+ hearts: LiveCounter;
+ likes: LiveCounter;
+ }>;
+ };
+
+ declare global {
+ export interface ObjectsTypes {
+ root: MyCustomRoot;
+ }
+ }
+ ```
+
+blang[javascript].
+
+ This enables TypeScript to infer the correct types when accessing and mutating LiveObjects:
+
+blang[javascript].
+
+ ```[javascript]
+ // LiveMap<{ reactions: LiveMap<{ hearts: LiveCounter; likes: LiveCounter }> }>
+ const root = await channel.objects.getRoot();
+
+ // LiveMap<{ hearts: LiveCounter; likes: LiveCounter }>
+ const reactions = root.get('reactions');
+
+ // LiveCounter
+ const likes = reactions.get('likes');
+
+ reactions.set('hearts', 1); // Error: Argument of type 'number' is not assignable to parameter of type 'LiveCounter'.ts(2345)
+ ```
+
+blang[javascript].
+
+ h2(#getroot). Typing channel.objects.getRoot()
+
+ You can pass a type parameter directly to the @channel.objects.getRoot()@ method call to type the root object for a channel explicitly:
+
+blang[javascript].
+
+ ```[javascript]
+ // Define types for different root objects
+ type ReactionsRoot = {
+ hearts: LiveCounter;
+ likes: LiveCounter;
+ };
+
+ type PollsRoot = {
+ currentPoll: LiveMap;
+ };
+
+ // LiveMap<{ hearts: LiveCounter; likes: LiveCounter }>
+ const reactionsRoot = await reactionsChannel.objects.getRoot();
+
+ // LiveMap<{ currentPoll: LiveMap }>
+ const pollsRoot = await pollsChannel.objects.getRoot();
+ ```
+
+blang[javascript].
+
+ Typing @channel.objects.getRoot()@ is particularly useful when your application uses multiple channels, each with a different object structure.
diff --git a/content/metadata-stats/stats.textile b/content/metadata-stats/stats.textile
index 109d39b471..a5d2d97f83 100644
--- a/content/metadata-stats/stats.textile
+++ b/content/metadata-stats/stats.textile
@@ -381,18 +381,22 @@ All messages metrics include all messages types, such as those sent and received
| messages.all.all.uncompressedData | Total uncompressed message size, excluding delta compression. |
| messages.all.all.failed | Total number of messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side. |
| messages.all.all.refused | Total number of messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions.|
-| messages.all.messages.count | Total message count, excluding presence and state messages. |
-| messages.all.messages.billableCount | Total billable message count, excluding presence and state messages. |
-| messages.all.messages.data | Total message size, excluding presence and state messages. |
+| messages.all.messages.count | Total message count, excluding presence and object messages. |
+| messages.all.messages.billableCount | Total billable message count, excluding presence and object messages. |
+| messages.all.messages.data | Total message size, excluding presence and object messages. |
| messages.all.messages.uncompressedData | Total number of messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side. |
-| messages.all.messages.failed | Total number of messages excluding presence and state messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side. |
-| messages.all.messages.refused | Total number of messages excluding presence and state messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. |
+| messages.all.messages.failed | Total number of messages excluding presence and object messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side. |
+| messages.all.messages.refused | Total number of messages excluding presence and object messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. |
| messages.all.presence.count | Total presence message count. |
| messages.all.presence.billableCount | Total billable presence message count. |
| messages.all.presence.data | Total presence message size. |
| messages.all.presence.uncompressedData | Total uncompressed presence message size, excluding delta compression. |
-| messages.all.messages.failed | Total number of presence messages excluding presence and state messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side. |
-| messages.all.messages.refused | Total number of presence messages excluding presence and state messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. |
+| messages.all.objects.count | Total objects message count. |
+| messages.all.objects.billableCount | Total billable objects message count. |
+| messages.all.objects.data | Total objects message size. |
+| messages.all.objects.uncompressedData | Total uncompressed objects message size, excluding delta compression. |
+| messages.all.messages.failed | Total number of presence messages excluding presence and object messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side. |
+| messages.all.messages.refused | Total number of presence messages excluding presence and object messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. |
h3(#inbound). Inbound messages
@@ -404,50 +408,50 @@ Inbound messages metrics include those messages that are sent by clients and rec
| messages.inbound.realtime.all.uncompressedData | Total uncompressed inbound realtime message size, excluding delta compression, received by Ably from clients. |
| messages.inbound.realtime.all.failed | Total number of inbound realtime messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
| messages.inbound.realtime.all.refused | Total number of inbound realtime messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. |
-| messages.inbound.realtime.messages.count | Total inbound realtime message count, excluding presence and state messages, received by Ably from clients. |
-| messages.inbound.realtime.messages.data | Total inbound realtime message size, excluding presence and state messages, received by Ably from clients. |
-| messages.inbound.realtime.messages.uncompressedData | Total uncompressed inbound realtime message size, received by Ably from clients. This excludes delta compression, and presence and state messages. |
-| messages.inbound.realtime.messages.failed | Total number of inbound realtime messages excluding presence and state messages that failed These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side. |
-| messages.inbound.realtime.messages.refused | Total number of inbound realtime messages excluding presence and state messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions.|
+| messages.inbound.realtime.messages.count | Total inbound realtime message count, excluding presence and object messages, received by Ably from clients. |
+| messages.inbound.realtime.messages.data | Total inbound realtime message size, excluding presence and object messages, received by Ably from clients. |
+| messages.inbound.realtime.messages.uncompressedData | Total uncompressed inbound realtime message size, received by Ably from clients. This excludes delta compression, and presence and object messages. |
+| messages.inbound.realtime.messages.failed | Total number of inbound realtime messages excluding presence and object messages that failed These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side. |
+| messages.inbound.realtime.messages.refused | Total number of inbound realtime messages excluding presence and object messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions.|
| messages.inbound.realtime.presence.count | Total inbound realtime presence message count, received by Ably from clients. |
| messages.inbound.realtime.presence.data | Total inbound realtime presence message size, received by Ably from clients. |
| messages.inbound.realtime.presence.uncompressedData | Total uncompressed inbound realtime presence message size, excluding delta compression, received by Ably from clients. |
| messages.inbound.realtime.presence.failed | Total number of inbound realtime presence messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
| messages.inbound.realtime.presence.refused | Total number of inbound realtime presence messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. |
-| messages.inbound.realtime.state.count | Total inbound realtime state message count, received by Ably from clients. |
-| messages.inbound.realtime.state.data | Total inbound realtime state message size, received by Ably from clients. |
-| messages.inbound.realtime.state.uncompressedData | Total uncompressed inbound realtime state message size received by Ably from clients. |
-| messages.inbound.realtime.state.failed | Total number of inbound realtime state messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
-| messages.inbound.realtime.state.refused | Total number of inbound realtime state messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. |
+| messages.inbound.realtime.objects.count | Total inbound realtime object message count, received by Ably from clients. |
+| messages.inbound.realtime.objects.data | Total inbound realtime object message size, received by Ably from clients. |
+| messages.inbound.realtime.objects.uncompressedData | Total uncompressed inbound realtime object message size received by Ably from clients. |
+| messages.inbound.realtime.objects.failed | Total number of inbound realtime object messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
+| messages.inbound.realtime.objects.refused | Total number of inbound realtime object messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. |
| messages.inbound.rest.all.count | Total inbound REST message count, received by Ably from clients. |
| messages.inbound.rest.all.data | Total inbound REST message size, received by Ably from clients. |
| messages.inbound.rest.all.uncompressedData | Total uncompressed inbound REST message size, excluding delta compression, received by Ably from clients. |
| messages.inbound.rest.all.failed | Total number of inbound REST messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
| messages.inbound.rest.all.refused | Total number of inbound REST messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. |
-| messages.inbound.rest.messages.count | Total inbound REST message count, excluding presence and state messages, received by Ably from clients. |
-| messages.inbound.rest.messages.data | Total inbound REST message size, excluding presence and state messages, received by Ably from clients. |
-| messages.inbound.rest.messages.uncompressedData | Total uncompressed inbound REST message size, received by Ably from clients. This excludes delta compression, and presence and state messages. |
-| messages.inbound.rest.messages.failed | Total number of inbound REST messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. This excludes presence and state messages. |
-| messages.inbound.rest.messages.refused | Total number of inbound REST messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. This excludes presence and state messages. |
+| messages.inbound.rest.messages.count | Total inbound REST message count, excluding presence and object messages, received by Ably from clients. |
+| messages.inbound.rest.messages.data | Total inbound REST message size, excluding presence and object messages, received by Ably from clients. |
+| messages.inbound.rest.messages.uncompressedData | Total uncompressed inbound REST message size, received by Ably from clients. This excludes delta compression, and presence and object messages. |
+| messages.inbound.rest.messages.failed | Total number of inbound REST messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. This excludes presence and object messages. |
+| messages.inbound.rest.messages.refused | Total number of inbound REST messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. This excludes presence and object messages. |
| messages.inbound.rest.presence.count | Total inbound REST presence message count, received by Ably from clients. |
| messages.inbound.rest.presence.data | Total inbound REST presence message size, received by Ably from clients. |
| messages.inbound.rest.presence.uncompressedData | Total uncompressed inbound REST presence message size, excluding delta compression, received by Ably from clients. |
| messages.inbound.rest.presence.failed | Total number of inbound REST presence messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
| messages.inbound.rest.presence.refused | Total number of inbound REST presence messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions.|
-| messages.inbound.rest.state.count | Total inbound REST state message count, received by Ably from clients. |
-| messages.inbound.rest.state.data | Total inbound REST state message size, received by Ably from clients. |
-| messages.inbound.rest.state.uncompressedData | Total uncompressed inbound REST state message size, excluding delta compression received by Ably from clients. |
-| messages.inbound.rest.state.failed | Total number of inbound REST state messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
-| messages.inbound.rest.state.refused | Total number of inbound REST state messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions.|
+| messages.inbound.rest.objects.count | Total inbound REST object message count, received by Ably from clients. |
+| messages.inbound.rest.objects.data | Total inbound REST object message size, received by Ably from clients. |
+| messages.inbound.rest.objects.uncompressedData | Total uncompressed inbound REST object message size, excluding delta compression received by Ably from clients. |
+| messages.inbound.rest.objects.failed | Total number of inbound REST object messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
+| messages.inbound.rest.objects.refused | Total number of inbound REST object messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions.|
| messages.inbound.all.all.count | Total inbound message count, received by Ably from clients. |
| messages.inbound.all.all.data | Total inbound message size, received by Ably from clients. |
| messages.inbound.all.all.uncompressedData | Total uncompressed inbound message size, excluding delta compression, received by Ably from clients. |
| messages.inbound.all.all.failed | Total number of inbound messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
| messages.inbound.all.all.refused | Total number of inbound messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits, malformed messages, or incorrect client permissions. |
-| messages.inbound.all.messages.count | Total inbound message count, excluding presence and state messages, received by Ably from clients. |
-| messages.inbound.all.messages.data | Total inbound message size, excluding presence and state messages, received by Ably from clients. |
-| messages.inbound.all.messages.uncompressedData | Total uncompressed inbound message size, received by Ably from clients. This excludes delta compression, and presence and state messages. |
-| messages.inbound.all.messages.failed | Total number of inbound messages excluding presence and state messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
+| messages.inbound.all.messages.count | Total inbound message count, excluding presence and object messages, received by Ably from clients. |
+| messages.inbound.all.messages.data | Total inbound message size, excluding presence and object messages, received by Ably from clients. |
+| messages.inbound.all.messages.uncompressedData | Total uncompressed inbound message size, received by Ably from clients. This excludes delta compression, and presence and object messages. |
+| messages.inbound.all.messages.failed | Total number of inbound messages excluding presence and object messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
| messages.inbound.all.presence.count | Total inbound presence message count, received by Ably from clients. |
| messages.inbound.all.presence.data | Total inbound presence message size, received by Ably from clients. |
| messages.inbound.all.presence.uncompressedData | Total uncompressed inbound presence message size, excluding delta compression, received by Ably from clients. |
@@ -465,47 +469,47 @@ Outbound message metrics include those messages that are sent outbound from Ably
| messages.outbound.realtime.all.uncompressedData | Total uncompressed outbound realtime message size, excluding delta compression, sent from Ably to clients. |
| messages.outbound.realtime.all.failed | Total number of outbound realtime messages that failed. These are messages which did not succeed for some reason other than Ably rejecting them, such as rejection by an external integration target. |
| messages.outbound.realtime.all.refused | Total number of outbound realtime messages that were refused by Ably. This is generally due to "rate limiting":/docs/pricing/limits. |
-| messages.outbound.realtime.messages.count | Total outbound realtime message count, excluding presence and state messages, sent from Ably to clients. |
-| messages.outbound.realtime.messages.billableCount | Total billable outbound realtime message count, excluding presence and state messages, sent from Ably to clients. |
-| messages.outbound.realtime.messages.data | Total outbound realtime message size, excluding presence and state messages, sent from Ably to clients. |
-| messages.outbound.realtime.messages.uncompressedData | Total uncompressed outbound realtime message size, sent from Ably to clients. This excludes delta compression, and presence and state messages. |
-| messages.outbound.realtime.messages.failed | Total number of outbound realtime messages excluding presence and state messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
-| messages.outbound.realtime.messages.refused | Total number of outbound realtime messages excluding presence and state messages that were refused by Ably. This is generally due to "rate limiting":/docs/pricing/limits. |
+| messages.outbound.realtime.messages.count | Total outbound realtime message count, excluding presence and object messages, sent from Ably to clients. |
+| messages.outbound.realtime.messages.billableCount | Total billable outbound realtime message count, excluding presence and object messages, sent from Ably to clients. |
+| messages.outbound.realtime.messages.data | Total outbound realtime message size, excluding presence and object messages, sent from Ably to clients. |
+| messages.outbound.realtime.messages.uncompressedData | Total uncompressed outbound realtime message size, sent from Ably to clients. This excludes delta compression, and presence and object messages. |
+| messages.outbound.realtime.messages.failed | Total number of outbound realtime messages excluding presence and object messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
+| messages.outbound.realtime.messages.refused | Total number of outbound realtime messages excluding presence and object messages that were refused by Ably. This is generally due to "rate limiting":/docs/pricing/limits. |
| messages.outbound.realtime.presence.count | Total outbound realtime presence message count, sent from Ably to clients. |
| messages.outbound.realtime.presence.billableCount | Total billable outbound realtime presence message count, sent from Ably to clients. |
| messages.outbound.realtime.presence.data | Total outbound realtime presence message size, sent from Ably to clients. |
| messages.outbound.realtime.presence.uncompressedData | Total uncompressed outbound realtime presence message size, excluding delta compression, sent from Ably to clients. |
| messages.outbound.realtime.presence.failed | Total number of outbound realtime presence messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side. |
| messages.outbound.realtime.presence.refused | Total number of outbound realtime presence messages that were refused by Ably. This is generally due to "rate limiting":/docs/pricing/limits. |
-| messages.outbound.realtime.state.count | Total outbound realtime state message count, sent from Ably to clients. |
-| messages.outbound.realtime.state.billableCount | Total billable outbound realtime state message count, sent from Ably to clients. |
-| messages.outbound.realtime.state.data | Total outbound realtime state message size, sent from Ably to clients. |
-| messages.outbound.realtime.state.uncompressedData | Total uncompressed outbound realtime presence message size, sent from Ably to clients. |
+| messages.outbound.realtime.objects.count | Total outbound realtime object message count, sent from Ably to clients. |
+| messages.outbound.realtime.objects.billableCount | Total billable outbound realtime object message count, sent from Ably to clients. |
+| messages.outbound.realtime.objects.data | Total outbound realtime object message size, sent from Ably to clients. |
+| messages.outbound.realtime.objects.uncompressedData | Total uncompressed outbound realtime presence message size, sent from Ably to clients. |
| messages.outbound.rest.all.count | Total outbound REST message count, sent from Ably to clients. |
| messages.outbound.rest.all.data | Total outbound REST message size, sent from Ably to clients. |
| messages.outbound.rest.all.uncompressedData | Total uncompressed outbound REST message size, excluding delta compression, sent from Ably to clients. |
| messages.outbound.rest.all.refused | Total number of messages that would have been broadcast to realtime subscribers as a result of a REST publish attempt that was refused for breaching account-wide "message rate limits":/docs/pricing/limits. |
-| messages.outbound.rest.messages.count | Total outbound REST message count, excluding presence and state messages, sent from Ably to clients. |
-| messages.outbound.rest.messages.data | Total outbound REST message size, excluding presence and state messages, sent from Ably to clients. |
-| messages.outbound.rest.messages.uncompressedData | Total uncompressed outbound REST message size, sent from Ably to clients. This excludes delta compression, and presence and state messages. |
-| messages.outbound.rest.messages.refused | Total number of messages that would have been broadcast to realtime subscribers as a result of a REST publish attempt that was refused for breaching account-wide "message rate limit":/docs/pricing/limits. This excludes presence and state messages. |
+| messages.outbound.rest.messages.count | Total outbound REST message count, excluding presence and object messages, sent from Ably to clients. |
+| messages.outbound.rest.messages.data | Total outbound REST message size, excluding presence and object messages, sent from Ably to clients. |
+| messages.outbound.rest.messages.uncompressedData | Total uncompressed outbound REST message size, sent from Ably to clients. This excludes delta compression, and presence and object messages. |
+| messages.outbound.rest.messages.refused | Total number of messages that would have been broadcast to realtime subscribers as a result of a REST publish attempt that was refused for breaching account-wide "message rate limit":/docs/pricing/limits. This excludes presence and object messages. |
| messages.outbound.rest.presence.count | Total outbound REST presence message count, sent from Ably to clients. |
| messages.outbound.rest.presence.data | Total outbound REST presence message size, sent from Ably to clients. |
| messages.outbound.rest.presence.uncompressedData | Total uncompressed outbound REST presence message size, excluding delta compression, sent from Ably to clients. |
-| messages.outbound.rest.state.count | Total outbound REST state message count, sent from Ably to clients. |
-| messages.outbound.rest.state.data | Total outbound REST state message size, sent from Ably to clients. |
-| messages.outbound.rest.state.uncompressedData | Total uncompressed outbound REST state message size, excluding delta compression, sent from Ably to clients. |
-| messages.outbound.rest.state.refused | Total number of state messages that would have been broadcast to realtime subscribers as a result of a REST publish attempt that was refused for breaching account-wide "message rate limit":/docs/pricing/limits. |
+| messages.outbound.rest.objects.count | Total outbound REST object message count, sent from Ably to clients. |
+| messages.outbound.rest.objects.data | Total outbound REST object message size, sent from Ably to clients. |
+| messages.outbound.rest.objects.uncompressedData | Total uncompressed outbound REST object message size, excluding delta compression, sent from Ably to clients. |
+| messages.outbound.rest.objects.refused | Total number of object messages that would have been broadcast to realtime subscribers as a result of a REST publish attempt that was refused for breaching account-wide "message rate limit":/docs/pricing/limits. |
| messages.outbound.webhook.all.count | Total outbound webhook message count, sent from Ably to clients using webhooks. |
| messages.outbound.webhook.all.data | Total outbound webhook message size, sent from Ably to clients using webhooks. |
| messages.outbound.webhook.all.uncompressedData | Total uncompressed outbound webhook message size, excluding delta compression, sent from Ably to clients using webhooks. |
| messages.outbound.webhook.all.failed | Total number of outbound webhook messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as rejection by an external integration target. |
| messages.outbound.webhook.all.refused | Total number of outbound webhook messages that were refused by Ably. This is generally due to "rate limiting":/docs/pricing/limits. |
-| messages.outbound.webhook.messages.count | Total outbound webhook message count, sent from Ably to clients using webhooks. This excludes presence and state messages.|
-| messages.outbound.webhook.messages.data | Total outbound webhook message size, sent from Ably to clients using webhooks. This excludes presence and state messages. |
-| messages.outbound.webhook.messages.uncompressedData | Total uncompressed outbound webhook message size, sent from Ably to clients using webhooks. This excludes delta compression, and presence and state messages. |
-| messages.outbound.webhook.messages.failed | Total number of outbound webhook messages excluding presence and state messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them such as rejection by an external integration target. |
-| messages.outbound.webhook.messages.refused | Total number of outbound webhook messages excluding presence and state messages that were refused by Ably. This is generally due to "rate limiting":/docs/pricing/limits. |
+| messages.outbound.webhook.messages.count | Total outbound webhook message count, sent from Ably to clients using webhooks. This excludes presence and object messages.|
+| messages.outbound.webhook.messages.data | Total outbound webhook message size, sent from Ably to clients using webhooks. This excludes presence and object messages. |
+| messages.outbound.webhook.messages.uncompressedData | Total uncompressed outbound webhook message size, sent from Ably to clients using webhooks. This excludes delta compression, and presence and object messages. |
+| messages.outbound.webhook.messages.failed | Total number of outbound webhook messages excluding presence and object messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them such as rejection by an external integration target. |
+| messages.outbound.webhook.messages.refused | Total number of outbound webhook messages excluding presence and object messages that were refused by Ably. This is generally due to "rate limiting":/docs/pricing/limits. |
| messages.outbound.webhook.presence.count | Total outbound webhook presence message count, sent from Ably to clients using webhooks. |
| messages.outbound.webhook.presence.data | Total outbound webhook presence message size, sent from Ably to clients using webhooks. |
| messages.outbound.webhook.presence.uncompressedData | Total uncompressed outbound webhook presence message size, excluding delta compression, sent from Ably to clients using webhooks. |
@@ -516,11 +520,11 @@ Outbound message metrics include those messages that are sent outbound from Ably
| messages.outbound.sharedQueue.all.uncompressedData | Total uncompressed Ably Queue message size, excluding delta compression, sent from Ably to an Ably Queue using an integration rule. |
| messages.outbound.sharedQueue.all.failed | Total number of Ably Queue messages that failed because they were rejected by RabbitMQ for some reason. |
| messages.outbound.sharedQueue.all.refused | Total number of Ably Queue messages that Ably refused to send. This is generally due to "rate limiting":/docs/pricing/limits. |
-| messages.outbound.sharedQueue.messages.count | Total Ably Queue message count, sent from Ably to an Ably Queue using an integration rule. This excludes presence and state messages. |
-| messages.outbound.sharedQueue.messages.data | Total Ably Queue message size, sent from Ably to an Ably Queue using an integration rule. This excludes presence and state messages. |
-| messages.outbound.sharedQueue.messages.uncompressedData | Total uncompressed Ably Queue message size, sent from Ably to an Ably Queue using an integration rule. This excludes delta compression, and presence and state messages. |
-| messages.outbound.sharedQueue.messages.failed | Total number of Ably Queue messages, excluding presence and state messages, that failed because they were rejected by RabbitMQ for some reason. |
-| messages.outbound.sharedQueue.messages.refused | Total number of Ably Queue messages, excluding presence and state messages, that Ably refused to send. |
+| messages.outbound.sharedQueue.messages.count | Total Ably Queue message count, sent from Ably to an Ably Queue using an integration rule. This excludes presence and object messages. |
+| messages.outbound.sharedQueue.messages.data | Total Ably Queue message size, sent from Ably to an Ably Queue using an integration rule. This excludes presence and object messages. |
+| messages.outbound.sharedQueue.messages.uncompressedData | Total uncompressed Ably Queue message size, sent from Ably to an Ably Queue using an integration rule. This excludes delta compression, and presence and object messages. |
+| messages.outbound.sharedQueue.messages.failed | Total number of Ably Queue messages, excluding presence and object messages, that failed because they were rejected by RabbitMQ for some reason. |
+| messages.outbound.sharedQueue.messages.refused | Total number of Ably Queue messages, excluding presence and object messages, that Ably refused to send. |
| messages.outbound.sharedQueue.presence.count | Total Ably Queue presence message count, sent from Ably to an Ably Queue using an integration rule. |
| messages.outbound.sharedQueue.presence.data | Total Ably Queue presence message size, sent from Ably to an Ably Queue using an integration rule. |
| messages.outbound.sharedQueue.presence.uncompressedData | Total uncompressed Ably Queue presence message size, excluding delta compression, sent from Ably to an Ably Queue using an integration rule. |
@@ -531,11 +535,11 @@ Outbound message metrics include those messages that are sent outbound from Ably
| messages.outbound.externalQueue.all.uncompressedData | Total uncompressed Firehose message size, excluding delta compression, sent from Ably to an external target using a Firehose integration rule. |
| messages.outbound.externalQueue.all.failed | Total number of Firehose messages that failed because they were rejected by the external integration target for some reason. |
| messages.outbound.externalQueue.all.refused | Total number of Firehose messages that Ably refused to send. This is generally due to "rate limiting":/docs/pricing/limits. |
-| messages.outbound.externalQueue.messages.count | Total Firehose message count, sent from Ably to an external target using a Firehose integration rule. This excludes presence and state messages. |
-| messages.outbound.externalQueue.messages.data | Total Firehose message size, sent from Ably to an external target using a Firehose integration rule. This excludes presence and state messages. |
-| messages.outbound.externalQueue.messages.uncompressedData | Total uncompressed Firehose message size, sent from Ably to an external target using a Firehose integration rule. This excludes delta compression, and presence and state messages. |
-| messages.outbound.externalQueue.messages.failed | Total number of Firehose messages, excluding presence and state messages, that failed because they were rejected by the external integration target for some reason. |
-| messages.outbound.externalQueue.messages.refused | Total number of Firehose messages, excluding presence and state messages, that Ably refused to send. This is generally due to "rate limiting":/docs/pricing/limits. |
+| messages.outbound.externalQueue.messages.count | Total Firehose message count, sent from Ably to an external target using a Firehose integration rule. This excludes presence and object messages. |
+| messages.outbound.externalQueue.messages.data | Total Firehose message size, sent from Ably to an external target using a Firehose integration rule. This excludes presence and object messages. |
+| messages.outbound.externalQueue.messages.uncompressedData | Total uncompressed Firehose message size, sent from Ably to an external target using a Firehose integration rule. This excludes delta compression, and presence and object messages. |
+| messages.outbound.externalQueue.messages.failed | Total number of Firehose messages, excluding presence and object messages, that failed because they were rejected by the external integration target for some reason. |
+| messages.outbound.externalQueue.messages.refused | Total number of Firehose messages, excluding presence and object messages, that Ably refused to send. This is generally due to "rate limiting":/docs/pricing/limits. |
| messages.outbound.externalQueue.presence.count | Total Firehose presence message count, sent from Ably to an external target using a Firehose integration rule. |
| messages.outbound.externalQueue.presence.data | Total Firehose presence message size, sent from Ably to an external target using a Firehose integration rule. |
| messages.outbound.externalQueue.presence.uncompressedData | Total uncompressed Firehose presence message size, sent from Ably to an external target using a Firehose integration rule. This excludes delta compression. |
@@ -546,11 +550,11 @@ Outbound message metrics include those messages that are sent outbound from Ably
| messages.outbound.httpEvent.all.uncompressedData | Total uncompressed size of messages sent by a HTTP trigger. Typically a serverless function on a service such as AWS Lambda, Google Cloud Functions, or Azure Functions. This excludes delta compression. |
| messages.outbound.httpEvent.all.failed | Total number of messages sent by a HTTP trigger that failed, because they were rejected by the external endpoint for some reason. |
| messages.outbound.httpEvent.all.refused | Total number of messages sent by a HTTP trigger that Ably refused to send. This is generally due to "rate limiting":/docs/pricing/limits. |
-| messages.outbound.httpEvent.messages.count | Total messages sent by a HTTP trigger. Typically a serverless function on a service such as AWS Lambda, Google Cloud Functions, or Azure Functions. This excludes presence and state messages. |
-| messages.outbound.httpEvent.messages.data | Total size of messages sent by a HTTP trigger. Typically a serverless function on a service such as AWS Lambda, Google Cloud Functions, or Azure Functions. This excludes presence and state messages. |
-| messages.outbound.httpEvent.messages.uncompressedData | Total uncompressed size of messages sent by a HTTP trigger. Typically a serverless function on a service such as AWS Lambda, Google Cloud Functions, or Azure Functions. This excludes delta compression, and presence and state messages. |
-| messages.outbound.httpEvent.messages.failed | Total number of messages sent by a HTTP trigger, excluding presence and state messages, that failed because they were rejected by the external endpoint for some reason. |
-| messages.outbound.httpEvent.messages.refused | Total number of messages sent by a HTTP trigger, excluding presence and state messages, that Ably refused to send. This is generally due to "rate limiting":/docs/pricing/limits. |
+| messages.outbound.httpEvent.messages.count | Total messages sent by a HTTP trigger. Typically a serverless function on a service such as AWS Lambda, Google Cloud Functions, or Azure Functions. This excludes presence and object messages. |
+| messages.outbound.httpEvent.messages.data | Total size of messages sent by a HTTP trigger. Typically a serverless function on a service such as AWS Lambda, Google Cloud Functions, or Azure Functions. This excludes presence and object messages. |
+| messages.outbound.httpEvent.messages.uncompressedData | Total uncompressed size of messages sent by a HTTP trigger. Typically a serverless function on a service such as AWS Lambda, Google Cloud Functions, or Azure Functions. This excludes delta compression, and presence and object messages. |
+| messages.outbound.httpEvent.messages.failed | Total number of messages sent by a HTTP trigger, excluding presence and object messages, that failed because they were rejected by the external endpoint for some reason. |
+| messages.outbound.httpEvent.messages.refused | Total number of messages sent by a HTTP trigger, excluding presence and object messages, that Ably refused to send. This is generally due to "rate limiting":/docs/pricing/limits. |
| messages.outbound.httpEvent.presence.count | Total presence messages sent by a HTTP trigger. Typically a serverless function on a service such as AWS Lambda, Google Cloud Functions, or Azure Functions. |
| messages.outbound.httpEvent.presence.data | Total size of presence messages sent by a HTTP trigger. Typically a serverless function on a service such as AWS Lambda, Google Cloud Functions, or Azure Functions. |
| messages.outbound.httpEvent.presence.uncompressedData | Total uncompressed size of presence messages sent by a HTTP trigger. Typically serverless functions on a service such as AWS Lambda, Google Cloud Functions, or Azure Functions. This excludes delta compression. |
@@ -561,11 +565,11 @@ Outbound message metrics include those messages that are sent outbound from Ably
| messages.outbound.push.all.uncompressedData | Total uncompressed push message size, excluding delta compression, pushed to devices via a push notifications transport such as FCM or APNS. |
| messages.outbound.push.all.failed | Total number of push messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as rejection by APNS or FCM, or a service issue on Ably's side. |
| messages.outbound.push.all.refused | Total number of push messages that were refused by Ably. For example, due to "rate limiting":/docs/pricing/limits. |
-| messages.outbound.push.messages.count | Total push message count, excluding delta compression, and presence and state messages, pushed to devices via a push notifications transport such as FCM or APNS.|
-| messages.outbound.push.messages.data | Total push message size, excluding delta compression, and presence and state messages, pushed to devices via a push notifications transport such as FCM or APNS. |
-| messages.outbound.push.messages.uncompressedData | Total uncompressed push message size, excluding delta compression, and presence and state messages, pushed to devices via a push notifications transport such as FCM or APNS. |
-| messages.outbound.push.messages.failed | Total number of push messages, excluding presence and state messages, that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as rejection by APNS or FCM, or a service issue on Ably's side. |
-| messages.outbound.push.messages.refused | Total number of push messages, excluding presence and state messages, that were refused by Ably. For example due to "rate limiting":/docs/pricing/limits. |
+| messages.outbound.push.messages.count | Total push message count, excluding delta compression, and presence and object messages, pushed to devices via a push notifications transport such as FCM or APNS.|
+| messages.outbound.push.messages.data | Total push message size, excluding delta compression, and presence and object messages, pushed to devices via a push notifications transport such as FCM or APNS. |
+| messages.outbound.push.messages.uncompressedData | Total uncompressed push message size, excluding delta compression, and presence and object messages, pushed to devices via a push notifications transport such as FCM or APNS. |
+| messages.outbound.push.messages.failed | Total number of push messages, excluding presence and object messages, that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as rejection by APNS or FCM, or a service issue on Ably's side. |
+| messages.outbound.push.messages.refused | Total number of push messages, excluding presence and object messages, that were refused by Ably. For example due to "rate limiting":/docs/pricing/limits. |
| messages.outbound.push.presence.count | Total push presence message count, sent to devices via a push notifications transport such as FCM or APNS. |
| messages.outbound.push.presence.data | Total push presence message size, sent to devices via a push notifications transport such as FCM or APNS. |
| messages.outbound.push.presence.uncompressedData | Total uncompressed push presence message size, excluding delta compression, sent to devices via a push notifications transport such as FCM or APNS. |
@@ -577,12 +581,12 @@ Outbound message metrics include those messages that are sent outbound from Ably
| messages.outbound.all.all.uncompressedData | Total uncompressed outbound message size, excluding delta compression, sent from Ably to clients. |
| messages.outbound.all.all.failed | Total number of outbound messages that failed. These are messages which not succeed for some reason other than Ably explicitly refusing them, such as rejection by an external integration target, or a service issue on Ably's side. |
| messages.outbound.all.all.refused | Total number of outbound messages that were refused by Ably. This is generally due to "rate limiting":/docs/pricing/limits. |
-| messages.outbound.all.messages.count | Total outbound message count, excluding presence and state messages, sent from Ably to clients. |
-| messages.outbound.all.messages.billableCount | Total billable outbound message count, excluding presence and state messages, sent from Ably to clients. |
-| messages.outbound.all.messages.data | Total outbound message size, excluding presence and state messages, sent from Ably to clients. |
-| messages.outbound.all.messages.uncompressedData | Total uncompressed outbound message size, excluding delta compression, and presence and state messages, sent from Ably to clients. |
-| messages.outbound.all.messages.failed | Total number of outbound messages excluding presence and state messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as rejection by an external integration target, or a service issue on Ably's side. |
-| messages.outbound.all.messages.refused | Total number of outbound messages excluding presence and state messages that were refused by Ably. This is generally due to "rate limiting":/docs/pricing/limits. |
+| messages.outbound.all.messages.count | Total outbound message count, excluding presence and object messages, sent from Ably to clients. |
+| messages.outbound.all.messages.billableCount | Total billable outbound message count, excluding presence and object messages, sent from Ably to clients. |
+| messages.outbound.all.messages.data | Total outbound message size, excluding presence and object messages, sent from Ably to clients. |
+| messages.outbound.all.messages.uncompressedData | Total uncompressed outbound message size, excluding delta compression, and presence and object messages, sent from Ably to clients. |
+| messages.outbound.all.messages.failed | Total number of outbound messages excluding presence and object messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as rejection by an external integration target, or a service issue on Ably's side. |
+| messages.outbound.all.messages.refused | Total number of outbound messages excluding presence and object messages that were refused by Ably. This is generally due to "rate limiting":/docs/pricing/limits. |
| messages.outbound.all.presence.count | Total outbound presence message count, sent from Ably to clients. |
| messages.outbound.all.presence.billableCount | Total billable outbound presence message count, sent from Ably to clients. |
| messages.outbound.all.presence.data | Total outbound presence message size, sent from Ably to clients. |
@@ -598,9 +602,9 @@ Persisted messages metrics are calculated from the number of "messages stored":/
| messages.persisted.all.count | Total count of persisted messages. |
| messages.persisted.all.data | Total size of persisted messages. |
| messages.persisted.all.uncompressedData | Total uncompressed persisted message size, excluding delta compression. |
-| messages.persisted.messages.count | Total count of persisted messages, excluding presence and state messages. |
-| messages.persisted.messages.data | Total size of persisted messages, excluding presence and state messages. |
-| messages.persisted.messages.uncompressedData | Total uncompressed persisted message size, excluding delta compression, and presence and state messages. |
+| messages.persisted.messages.count | Total count of persisted messages, excluding presence and object messages. |
+| messages.persisted.messages.data | Total size of persisted messages, excluding presence and object messages. |
+| messages.persisted.messages.uncompressedData | Total uncompressed persisted message size, excluding delta compression, and presence and object messages. |
| messages.persisted.presence.count | Total count of persisted presence messages. |
| messages.persisted.presence.data | Total size of persisted presence messages. |
| messages.persisted.presence.uncompressedData | Total uncompressed persisted presence message size, excluding delta compression. |
diff --git a/content/partials/core-features/_authentication_capabilities.textile b/content/partials/core-features/_authentication_capabilities.textile
index adcd31561d..9b8f5f06cd 100644
--- a/content/partials/core-features/_authentication_capabilities.textile
+++ b/content/partials/core-features/_authentication_capabilities.textile
@@ -3,6 +3,8 @@ The following capability operations are available for API keys and issued tokens
- subscribe := can subscribe to messages and presence state change messages on channels, and get the presence set of a channel
- publish := can publish messages to channels
- presence := can register presence on a channel (enter, update and leave)
+- object-subscribe := can subscribe to updates to objects on a channel
+- object-publish := can update objects on a channel
- history := can retrieve message and presence state history on channels
- stats := can retrieve current and historical usage statistics for an app
- push-subscribe := can subscribe devices for push notifications
diff --git a/content/partials/general/events/_batched_events.textile b/content/partials/general/events/_batched_events.textile
index 71f83f9d4a..1339b58e81 100644
--- a/content/partials/general/events/_batched_events.textile
+++ b/content/partials/general/events/_batched_events.textile
@@ -142,7 +142,9 @@ The following is an example of a batched @channel lifecycle@ payload:
"subscribers": 1,
"presenceConnections": 1,
"presenceMembers": 0,
- "presenceSubscribers": 1
+ "presenceSubscribers": 1,
+ "objectPublishers": 1,
+ "objectSubscribers": 1
}
}
}
diff --git a/content/partials/types/_channel_details.textile b/content/partials/types/_channel_details.textile
index c618a7739c..1dd8925635 100644
--- a/content/partials/types/_channel_details.textile
+++ b/content/partials/types/_channel_details.textile
@@ -21,7 +21,9 @@ The following is an example of a @ChannelDetails@ JSON object:
"subscribers": 1,
"presenceConnections": 1,
"presenceMembers": 0,
- "presenceSubscribers": 1
+ "presenceSubscribers": 1,
+ "objectPublishers": 1,
+ "objectSubscribers": 1
}
}
}
@@ -47,3 +49,5 @@ The @occupancy@ attribute contains the @metrics@ attribute, which contains the f
- presenceSubscribers := the number of connections that are authorised to subscribe to presence messages __Type: @integer@__
- presenceConnections := the number of connections that are authorised to enter members into the presence channel __Type: @integer@__
- presenceMembers := the number of members currently entered into the presence channel __Type: @integer@__
+- objectPublishers := the number of connections that are authorised to publish updates to objects on the channel __Type: @integer@__
+- objectSubscribers := the number of connections that are authorised to subscribe to objects on the channel __Type: @integer@__
diff --git a/content/platform/index.textile b/content/platform/index.textile
index e57a103245..d565d748ae 100644
--- a/content/platform/index.textile
+++ b/content/platform/index.textile
@@ -67,6 +67,14 @@ Spaces is an abstraction built over Ably Pub/Sub. It utilizes Ably's platform to
Spaces is effective when building features such as interactive whiteboards, avatar stacks, and displaying and locking elements on a page, such as a cell in a spreadsheet, or a slide in a slideshow presentation.
+h3(#liveobjects). Ably LiveObjects
+
+Use Ably "LiveObjects":/docs/liveobjects to synchronize application state across users and devices in realtime. LiveObjects provides purpose-built APIs and data structures for managing shared state, and it automatically handles concurrency, conflict resolution, synchronization and persistence.
+
+LiveObjects is managed and persisted on Ably Pub/Sub channels. It utilizes Ably's platform to benefit from all of the same performance guarantees and scaling potential.
+
+LiveObjects is effective for use cases such as realtime voting and polling systems, collaborative applications, live leaderboards, multiplayer game state synchronization, and any other scenario where application data is shared, can be updated concurrently by many users, and needs to be synchronized in realtime.
+
h3(#livesync). Ably LiveSync
Use Ably "LiveSync":/docs/livesync to synchronize changes between your database and frontend clients. It provides support for PostgreSQL and MongoDB and uses the Ably platform to synchronize your application's data.
diff --git a/content/presence-occupancy/occupancy.textile b/content/presence-occupancy/occupancy.textile
index 55c040cadc..c4feb80047 100644
--- a/content/presence-occupancy/occupancy.textile
+++ b/content/presence-occupancy/occupancy.textile
@@ -18,6 +18,8 @@ The following are the metric categories that occupancy reports:
- presenceSubscribers := the number of connections that are authorized to subscribe to presence messages
- presenceConnections := the number of connections that are authorized to enter members into the presence channel
- presenceMembers := the number of members currently entered into the presence channel
+- objectPublishers := the number of connections that are authorized to publish updates to objects on the channel
+- objectSubscribers := the number of connections that are authorized to subscribe to objects on the channel
h2(#occupancy-payload). Occupancy payload structure
@@ -40,7 +42,9 @@ If occupancy is returned as a @[meta]occupancy@ event when subscribing to a chan
subscribers: 1,
presenceConnections: 1,
presenceMembers: 0,
- presenceSubscribers: 1
+ presenceSubscribers: 1,
+ objectPublishers: 1,
+ objectSubscribers: 1
}
},
encoding: null,
diff --git a/content/pricing/limits.textile b/content/pricing/limits.textile
index 8878543173..acae705134 100644
--- a/content/pricing/limits.textile
+++ b/content/pricing/limits.textile
@@ -82,6 +82,7 @@ Channel limits relate to the number, rate and membership of "channels":/docs/cha
| *Message publish rate per channel (per second)*
_the maximum rate at which messages can be published for each channel_
| 50 | 50 | 50 | 50 |
| *Presence members per channel*
_the maximum number of clients that can be simultaneously present on a channel_
| 200 | 200 | 200 | 200 |
| *Presence members per channel with "server-side batching":/docs/messages/batch#server-side enabled*
_the maximum number of clients that can be simultaneously present on a channel when server-side batching is enabled_
_the maximum number of objects that can be stored on a channel_
| 100 | 100 | 100 | 100 |
h2(#connection). Connection limits
diff --git a/content/storage-history/storage.textile b/content/storage-history/storage.textile
index 5977136484..8e742ad12b 100644
--- a/content/storage-history/storage.textile
+++ b/content/storage-history/storage.textile
@@ -17,7 +17,9 @@ The following diagram illustrates the default persistence of messages:
h2(#all-message-persistence). Persist all messages
-If you need to retain messages for longer than the default two minutes you can enable persisted history by setting a "channel rule":/docs/channels#rules. When persisted history is enabled for a channel any messages will be stored on disk. The time that messages will be stored for depends on your account package:
+If you need to retain messages for longer than the default two minutes you can enable persisted history by setting a "channel rule":/docs/channels#rules. When persisted history is enabled for a channel any messages will be stored on disk. Note that this does not apply to "object messages":/docs/liveobjects.
+
+The time that messages will be stored for depends on your account package:
|_. Package |_. Minimum |_. Maximum |
| Free | 24 hours | 24 hours |
@@ -36,7 +38,7 @@ Note that every message that is persisted to, or retrieved from, disk counts as
h2(#persist-last-message). Persist last message - 365 days
-You can persist just the last message sent to a channel for one year by setting a "channel rule":/docs/channels#rules. Note that this does not apply to "presence messages":/docs/presence-occupancy/presence.
+You can persist just the last message sent to a channel for one year by setting a "channel rule":/docs/channels#rules. Note that this does not apply to "presence messages":/docs/presence-occupancy/presence or "object messages":/docs/liveobjects.
Messages persisted for a year can be retrieved using the "rewind channel option":/docs/channels/options/rewind, or from the REST history API using "certain parameters":/docs/storage-history/history#channel-parameters.
diff --git a/src/components/Examples/ExamplesGrid.tsx b/src/components/Examples/ExamplesGrid.tsx
index 171c248ecf..d19dc5d992 100644
--- a/src/components/Examples/ExamplesGrid.tsx
+++ b/src/components/Examples/ExamplesGrid.tsx
@@ -33,6 +33,7 @@ const ExamplesGrid = ({
case 'assetTracking':
return 'text-green-600';
case 'liveObjects':
+ // Reusing Asset Tracking color as no examples exist for it yet.
return 'text-green-600';
default:
return 'text-orange-700';
diff --git a/src/components/ProductNavigation/ProductNavigation.tsx b/src/components/ProductNavigation/ProductNavigation.tsx
index d3be5b517a..3648b4a06b 100644
--- a/src/components/ProductNavigation/ProductNavigation.tsx
+++ b/src/components/ProductNavigation/ProductNavigation.tsx
@@ -28,6 +28,7 @@ const ProductNavigation = ({ currentProduct = 'channels' }: { currentProduct?: s
const onChannels = currentProduct === 'channels';
const onLiveSync = currentProduct === 'livesync';
const onChat = currentProduct === 'chat';
+ const onLiveObjects = currentProduct === 'liveobjects';
const onAssetTracking = currentProduct === 'asset-tracking';
return (
@@ -50,6 +51,9 @@ const ProductNavigation = ({ currentProduct = 'channels' }: { currentProduct?: s
Chat
+
+ LiveObjects
+
Asset Tracking
diff --git a/src/components/common/meta-title.test.ts b/src/components/common/meta-title.test.ts
index a0aaecc50e..215b105507 100644
--- a/src/components/common/meta-title.test.ts
+++ b/src/components/common/meta-title.test.ts
@@ -7,6 +7,7 @@ describe('getMetaTitle', () => {
['spaces', 'Ably Spaces', 'Setup'],
['livesync', 'Ably LiveSync', 'Begin'],
['chat', 'Ably Chat', 'Emojis'],
+ ['liveobjects', 'Ably LiveObjects', 'Setup'],
['asset-tracking', 'Ably Asset Tracking', 'Examples'],
['api-reference', 'API References', 'Setup'],
['pub_sub', 'Ably Pub/Sub', 'Authentication'],
diff --git a/src/components/common/meta-title.ts b/src/components/common/meta-title.ts
index 4eb2dc9f08..609317da7c 100644
--- a/src/components/common/meta-title.ts
+++ b/src/components/common/meta-title.ts
@@ -6,6 +6,7 @@ export const getMetaTitle = (title: string, product: ProductName): string => {
spaces: 'Ably Spaces',
livesync: 'Ably LiveSync',
chat: 'Ably Chat',
+ liveobjects: 'Ably LiveObjects',
'asset-tracking': 'Ably Asset Tracking',
'api-reference': 'API References',
pub_sub: 'Ably Pub/Sub',
diff --git a/src/data/content/homepage.ts b/src/data/content/homepage.ts
index b8f66634db..c98deb07a4 100644
--- a/src/data/content/homepage.ts
+++ b/src/data/content/homepage.ts
@@ -40,7 +40,7 @@ export default {
},
{
name: 'liveObjects',
- link: '/docs/objects',
+ link: '/docs/liveobjects',
},
],
},
diff --git a/src/data/index.ts b/src/data/index.ts
index 374d5aaedf..7a2ac779d9 100644
--- a/src/data/index.ts
+++ b/src/data/index.ts
@@ -1,6 +1,7 @@
import {
assetTrackingNavData,
chatNavData,
+ liveObjectsNavData,
liveSyncNavData,
platformNavData,
pubsubNavData,
@@ -27,6 +28,10 @@ export const productData = {
nav: spacesNavData,
languages: languageData.spaces,
},
+ liveObjects: {
+ nav: liveObjectsNavData,
+ languages: languageData.liveObjects,
+ },
liveSync: {
nav: liveSyncNavData,
languages: languageData.liveSync,
diff --git a/src/data/languages/languageData.ts b/src/data/languages/languageData.ts
index 9740a19cf7..3f337a9f49 100644
--- a/src/data/languages/languageData.ts
+++ b/src/data/languages/languageData.ts
@@ -3,13 +3,13 @@ import { LanguageData } from './types';
export default {
platform: {
- javascript: 2.7,
- nodejs: 2.7,
+ javascript: 2.9,
+ nodejs: 2.9,
},
pubsub: {
- javascript: 2.7,
- nodejs: 2.7,
- react: 2.7,
+ javascript: 2.9,
+ nodejs: 2.9,
+ react: 2.9,
csharp: 1.2,
flutter: 1.2,
java: 1.2,
@@ -31,6 +31,9 @@ export default {
javascript: 0.4,
react: 0.4,
},
+ liveObjects: {
+ javascript: 2.9,
+ },
liveSync: {
javascript: 0.4,
},
diff --git a/src/data/nav/index.ts b/src/data/nav/index.ts
index e50b2997bf..6092966fd7 100644
--- a/src/data/nav/index.ts
+++ b/src/data/nav/index.ts
@@ -1,8 +1,17 @@
import platformNavData from './platform';
import pubsubNavData from './pubsub';
import chatNavData from './chat';
+import liveObjectsNavData from './liveobjects';
import spacesNavData from './spaces';
import liveSyncNavData from './livesync';
import assetTrackingNavData from './assettracking';
-export { platformNavData, pubsubNavData, chatNavData, spacesNavData, liveSyncNavData, assetTrackingNavData };
+export {
+ platformNavData,
+ pubsubNavData,
+ chatNavData,
+ liveObjectsNavData,
+ spacesNavData,
+ liveSyncNavData,
+ assetTrackingNavData,
+};
diff --git a/src/data/nav/liveobjects.ts b/src/data/nav/liveobjects.ts
new file mode 100644
index 0000000000..0c4c2d0dba
--- /dev/null
+++ b/src/data/nav/liveobjects.ts
@@ -0,0 +1,101 @@
+import { NavProduct } from './types';
+
+export default {
+ name: 'Ably LiveObjects',
+ link: '/docs/liveobjects',
+ icon: {
+ closed: 'icon-product-liveobjects-mono',
+ open: 'icon-product-liveobjects',
+ },
+ content: [
+ {
+ name: 'Introduction',
+ pages: [
+ {
+ name: 'About LiveObjects',
+ link: '/docs/liveobjects',
+ },
+ ],
+ },
+ {
+ name: 'Getting started',
+ pages: [
+ {
+ name: 'Quickstart',
+ link: '/docs/liveobjects/quickstart',
+ },
+ ],
+ },
+ {
+ name: 'Concepts',
+ pages: [
+ {
+ name: 'Objects',
+ link: '/docs/liveobjects/concepts/objects',
+ },
+ {
+ name: 'Operations',
+ link: '/docs/liveobjects/concepts/operations',
+ },
+ {
+ name: 'Synchronization',
+ link: '/docs/liveobjects/concepts/synchronization',
+ },
+ ],
+ },
+ {
+ name: 'Object Types',
+ pages: [
+ {
+ name: 'LiveCounter',
+ link: '/docs/liveobjects/counter',
+ },
+ {
+ name: 'LiveMap',
+ link: '/docs/liveobjects/map',
+ },
+ ],
+ },
+ {
+ name: 'Advanced',
+ pages: [
+ {
+ name: 'Batch operations',
+ link: '/docs/liveobjects/batch',
+ },
+ {
+ name: 'Lifecycle events',
+ link: '/docs/liveobjects/lifecycle',
+ },
+ {
+ name: 'Typing',
+ link: '/docs/liveobjects/typing',
+ },
+ {
+ name: 'Using the REST API',
+ link: '/docs/liveobjects/rest-api-usage',
+ },
+ {
+ name: 'Inband Objects',
+ link: '/docs/liveobjects/inband-objects',
+ },
+ ],
+ },
+ ],
+ api: [
+ {
+ name: 'API References',
+ pages: [
+ {
+ link: 'https://ably.com/docs/sdk/js/v2.0/interfaces/ably.Objects.html',
+ name: 'JavaScript SDK',
+ external: true,
+ },
+ {
+ link: '/docs/api/liveobjects-rest',
+ name: 'REST API',
+ },
+ ],
+ },
+ ],
+} satisfies NavProduct;
diff --git a/src/data/types.ts b/src/data/types.ts
index f194a03243..f5886f7f26 100644
--- a/src/data/types.ts
+++ b/src/data/types.ts
@@ -3,7 +3,7 @@ import { LanguageData } from './languages/types';
import { NavProduct } from './nav/types';
const pageKeys = ['homepage'] as const;
-const productKeys = ['platform', 'pubsub', 'chat', 'spaces', 'liveSync', 'assetTracking'] as const;
+const productKeys = ['platform', 'pubsub', 'chat', 'spaces', 'liveObjects', 'liveSync', 'assetTracking'] as const;
export type ProductKey = (typeof productKeys)[number];
type PageKey = (typeof pageKeys)[number];
diff --git a/src/images/homepage/liveobjects.png b/src/images/homepage/liveobjects.png
new file mode 100644
index 0000000000..fdc86e503d
Binary files /dev/null and b/src/images/homepage/liveobjects.png differ
diff --git a/src/pages/docs/api/liveobjects-rest.tsx b/src/pages/docs/api/liveobjects-rest.tsx
new file mode 100644
index 0000000000..ac1ce44f93
--- /dev/null
+++ b/src/pages/docs/api/liveobjects-rest.tsx
@@ -0,0 +1,35 @@
+import { Link, withAssetPrefix } from 'gatsby';
+import Icon from '@ably/ui/core/Icon';
+import { useSiteMetadata } from '../../../hooks/use-site-metadata';
+import { Head } from '../../../components/Head';
+import { Loader } from '../../../components/Redoc';
+
+const LiveObjectsRestApi = () => {
+ const { canonicalUrl } = useSiteMetadata();
+ const canonical = canonicalUrl('/docs/api/liveobjects-rest');
+ const meta_title = 'LiveObjects REST API';
+ const meta_description = 'Ably provides the raw REST API for interacting with the LiveObjects stored on a channel.';
+ const liveObjectsRest = withAssetPrefix('/open-specs/liveobjects.yaml');
+
+ return (
+ <>
+
+
+
+
+
+ Ably LiveObjects
+
+ / {meta_title}
+
+
+
+ >
+ );
+};
+
+export default LiveObjectsRestApi;
diff --git a/src/templates/template-data.d.ts b/src/templates/template-data.d.ts
index 9776ab61e4..769839f439 100644
--- a/src/templates/template-data.d.ts
+++ b/src/templates/template-data.d.ts
@@ -35,13 +35,22 @@ export type AblyPageContext = {
script: string;
};
-export type ProductName = 'channels' | 'spaces' | 'livesync' | 'chat' | 'asset-tracking' | 'api-reference' | 'home';
+export type ProductName =
+ | 'channels'
+ | 'spaces'
+ | 'livesync'
+ | 'chat'
+ | 'liveobjects'
+ | 'asset-tracking'
+ | 'api-reference'
+ | 'home';
export type ProductTitle =
| 'Channels'
| 'Ably Spaces'
| 'Ably LiveSync'
| 'Ably Chat'
+ | 'Ably LiveObjects'
| 'Ably Asset Tracking'
| 'API References'
| 'Home'
diff --git a/static/open-specs/liveobjects.yaml b/static/open-specs/liveobjects.yaml
new file mode 100644
index 0000000000..17e30d6d72
--- /dev/null
+++ b/static/open-specs/liveobjects.yaml
@@ -0,0 +1,1225 @@
+openapi: 3.0.0
+info:
+ title: Ably LiveObjects REST API
+ version: 1.0.0
+ description: |
+ # LiveObjects API
+
+ LiveObjects provides a set of purpose-built APIs and data structures to handle the complexities of persisting and synchronizing state, freeing you to focus on building features instead of managing concurrency or conflict resolution.
+
+ LiveObjects enables you to store data as "objects" on a channel. These objects are automatically synchronized in realtime across all connected clients, and any conflicts that arise from concurrent updates are seamlessly resolved in the background.
+servers:
+ - url: https://main.realtime.ably.net/
+ description: Production server for Ably REST API
+
+paths:
+ /channels/{channelId}/objects:
+ get:
+ summary: Get a tree of objects
+ description: |
+ Fetches the list of objects stored on the channel.
+
+ In the list API, data values represent a concrete piece of data (a number, a string, etc) or a reference to another object. The key in the data value indicates the type that you can expect to receive in the value, for example, `{ "data": { "string" : "Ably Pub/Sub" }}`.
+
+ Maps are made of entries, which are a user-defined key (e.g. `myMapKey`) and a data value. Counters are data values containing a number.
+
+ For more information on objects, see the Ably LiveObjects documentation on [objects](/docs/liveobjects/concepts/objects).
+ parameters:
+ - name: channelId
+ in: path
+ required: true
+ description: The channel ID.
+ schema:
+ type: string
+ example: "my-app:likes:counter"
+ - name: values
+ in: query
+ required: false
+ description: Include the values in the response. Setting this to false returns a list of `objectIds` only.
+ schema:
+ type: boolean
+ default: false
+ - name: limit
+ in: query
+ required: false
+ description: Set the number of objects to be returned in each page.
+ schema:
+ type: integer
+ - name: cursor
+ in: query
+ required: false
+ description: The cursor used for pagination.
+ schema:
+ type: string
+ - name: metadata
+ in: query
+ required: false
+ description: Include object metadata in the response.
+ schema:
+ type: boolean
+ default: false
+ responses:
+ '200':
+ description: List of retrieved objects or object references.
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - type: array
+ items:
+ $ref: '#/components/schemas/ObjectWithDetails'
+ - type: array
+ items:
+ type: string
+ example: "counter:Nz1ZiNjqsDfkDjA61xarinqpWsqEGAAw2mzWWtvX2b8@1742481614000"
+ examples:
+ objectArray:
+ summary: Full response with object values (url param values=true)
+ value:
+ - objectId: "map:YIffJYRAP2k2e7ZP+xzequ9c5kDu1LfI/sEOKoWHvv4@1742479683000"
+ map:
+ entries:
+ myMapKey: { data: { string: "my map value" }}
+ myBoolean: { data: { boolean: true }}
+ myEncodedKey: { data: { bytes: "TGl2ZU9iamVjdHMgaXMgYXdlc29tZQo=", encoding: "base64" }}
+ - objectId: "counter:Nz1ZiNjqsDfkDjA61xarinqpWsqEGAAw2mzWWtvX2b8@1742481614000"
+ map:
+ entries:
+ counter: { data:{ number: 5 }}
+ myObjectRef: { data: { objectId: "counter:Nz1ZiNjqsDfkDjA61xarinqpWsqEGAAw2mzWWtvX2b8@1742481614000" }}
+ refArray:
+ summary: Just object references (url param values=false or not set)
+ value:
+ - "root"
+ - "map:YIffJYRAP2k2e7ZP+xzequ9c5kDu1LfI/sEOKoWHvv4@1742479683000"
+ - "counter:Nz1ZiNjqsDfkDjA61xarinqpWsqEGAAw2mzWWtvX2b8@1742481614000"
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AuthError'
+ - $ref: '#/components/schemas/ClientRestrictionNotSatisfied'
+ - $ref: '#/components/schemas/InvalidTokenKey'
+ - $ref: '#/components/schemas/MalformedCredential'
+ - $ref: '#/components/schemas/OperationObjectSubscribeUnauthorized'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/ApplicationDisabled'
+ - $ref: '#/components/schemas/ChannelStateNotEnabled'
+ - $ref: '#/components/schemas/StateOperationsOnlyAppliedOnRegularChannels'
+ '404':
+ description: Not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AppNotFound'
+ '429':
+ description: Too Many Requests
+ $ref: '#/components/schemas/RateLimitExceeded'
+ '500':
+ description: Internal server error.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/error'
+ post:
+ summary: Create or update objects.
+ description: |
+ Allows creating or updating objects on the channel by providing the operation and data in the request body. If `objectId` is not provided, the server will generate one and return it in the response.
+
+ For more information on operations, see the Ably LiveObjects documentation on [operations](/docs/liveobjects/concepts/operations).
+ parameters:
+ - name: channelId
+ in: path
+ required: true
+ description: The channel ID.
+ schema:
+ type: string
+ example: "my-app:likes:counter"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - type: object
+ properties:
+ id:
+ $ref: '#/components/parameters/idempotentParameter'
+ path:
+ $ref: '#/components/parameters/pathParameter'
+ operation:
+ $ref: '#/components/parameters/operationParameter'
+ data:
+ $ref: '#/components/parameters/dataParameter'
+ - type: array
+ items:
+ type: object
+ properties:
+ id:
+ $ref: '#/components/parameters/idempotentParameter'
+ path:
+ $ref: '#/components/parameters/pathParameter'
+ operation:
+ $ref: '#/components/parameters/operationParameter'
+ data:
+ $ref: '#/components/parameters/dataParameter'
+ examples:
+ singleOperation:
+ summary: Single operation example
+ value:
+ id: "myIdempotencyKey"
+ operation: "MAP_SET"
+ objectId: "map:cwhvmsq21tXtFDS02TQqPdIh..."
+ data:
+ key: "isActive"
+ value: { "boolean": true }
+ batchOperation:
+ summary: Batch operation example
+ value:
+ - id: "myIdempotencyKey:0"
+ operation: "MAP_SET"
+ objectId: "map:cwhvmsq21tXtFDS02TQqPdIhGGezcSc..."
+ data:
+ key: "isActive"
+ value: { "boolean": true }
+ - id: "myIdempotencyKey:1"
+ operation: "COUNTER_INC"
+ objectId: "counter:DXr2i8FHRGkLrHPccWhXKDj1VUX2s7ACvmT..."
+ data:
+ number: 1
+ createMap:
+ summary: Create a map
+ value:
+ operation: MAP_CREATE
+ data:
+ myMapKey: { string: "myMapValue" }
+ myOtherKey: { boolean: true }
+ setValueOnMap:
+ summary: Set a value on a map
+ value:
+ operation: MAP_SET
+ objectId: "map:99OSLwCFQsrs6GID4M8rfO_0sYmVwRADcE..."
+ data:
+ key: "foo"
+ value: { string: "bar" }
+ removeValueFromMap:
+ summary: Remove a value from a map
+ value:
+ operation: MAP_REMOVE
+ objectId: "map:99OSLwCFQsrs6GID4M8rfO_0sYmVwRADcECua_@174220..."
+ data:
+ key: "myMapKey"
+ createCounter:
+ summary: Create a counter
+ value:
+ operation: COUNTER_CREATE
+ data:
+ number: 3.1415926
+ incrementCounter:
+ summary: Increment a counter
+ value:
+ operation: COUNTER_INC
+ objectId: "counter:J7x6mAF8X5Ha60VBZb6GtXSgnKJQagNLgadUlg..."
+ data:
+ number: 2
+ responses:
+ '200':
+ description: Successfully created or updated objects.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ channel:
+ type: string
+ description: The channel ID.
+ example: ""
+ objectIds:
+ type: array
+ items:
+ type: string
+ description: The list of object IDs created or updated.
+ example: ["map:YIffJYRAP2k2e7ZP+xzequ9c5kDu1LfI/sEOKoWHvv4@1742479683000"]
+ '400':
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ "$ref": "#/components/schemas/CreateOrUpdateObjectsBadRequest"
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AuthError'
+ - $ref: '#/components/schemas/ClientRestrictionNotSatisfied'
+ - $ref: '#/components/schemas/InvalidTokenKey'
+ - $ref: '#/components/schemas/MalformedCredential'
+ - $ref: '#/components/schemas/OperationObjectSubscribeUnauthorized'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/ApplicationDisabled'
+ - $ref: '#/components/schemas/ChannelStateNotEnabled'
+ - $ref: '#/components/schemas/StateOperationsOnlyAppliedOnRegularChannels'
+ '404':
+ description: Not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AppNotFound'
+ '429':
+ description: Too many requests
+ $ref: '#/components/schemas/RateLimitExceeded'
+ /channels/{channelId}/objects/{objectId}:
+ get:
+ summary: Gets an object using an object ID.
+ description: |
+ Fetch a LiveObject stored on the channel in a tree structure.
+ parameters:
+ - name: channelId
+ in: path
+ required: true
+ description: The channel ID.
+ schema:
+ type: string
+ example: "my-app:likes:counter"
+ - name: objectId
+ in: path
+ required: true
+ description: The object ID.
+ schema:
+ type: string
+ example: "object-123"
+ default: root
+ - name: children
+ in: query
+ required: false
+ description: Causes the content of the objects to be included in the tree response, instead of just the objectIds.
+ schema:
+ type: boolean
+ default: false
+ responses:
+ '200':
+ description: Retrieve a LiveObject stored on the channel in a tree structure.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ObjectWithDetails'
+ examples:
+ withChildren:
+ summary: Expanded object with embedded children
+ value:
+ objectId: "root"
+ map:
+ entries:
+ myMapKey:
+ data:
+ string: "my map value"
+ myObjectRef:
+ data:
+ objectId: "counter:Nz1ZiNjqsDfkDjA61xarinqpWsqEGAAw2mzWWtvX2b8@1742481614000"
+ counter:
+ data:
+ number: 5
+ withoutChildren:
+ summary: Object references only
+ value:
+ objectId: "root"
+ map:
+ entries:
+ myMapKey:
+ data:
+ string: "my map value"
+ myObjectRef:
+ data:
+ objectId: "counter:Nz1ZiNjqsDfkDjA61xarinqpWsqEGAAw2mzWWtvX2b8@1742481614000"
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AuthError'
+ - $ref: '#/components/schemas/ClientRestrictionNotSatisfied'
+ - $ref: '#/components/schemas/InvalidTokenKey'
+ - $ref: '#/components/schemas/MalformedCredential'
+ - $ref: '#/components/schemas/OperationObjectSubscribeUnauthorized'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/ApplicationDisabled'
+ - $ref: '#/components/schemas/ChannelStateNotEnabled'
+ - $ref: '#/components/schemas/StateOperationsOnlyAppliedOnRegularChannels'
+ '404':
+ description: Not found
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AppNotFound'
+ - $ref: '#/components/schemas/ObjectNotFound'
+ '429':
+ description: Too many requests
+ $ref: '#/components/schemas/RateLimitExceeded'
+ /channels/{channelId}/objects/{objectId}/compact:
+ get:
+ summary: Get a compact tree of objects
+ description: |
+ Returns a tree structure of the objects in a concise format that's easier to unmarshal into data types that represent your state. To fetch the full object tree, use the objectId root.
+ parameters:
+ - name: channelId
+ in: path
+ required: true
+ description: The channel ID.
+ schema:
+ type: string
+ example: "my-app:chat:room-123"
+ - name: objectId
+ in: path
+ required: true
+ description: The object ID.
+ default: root
+ schema:
+ type: string
+ example: "object-123"
+ responses:
+ '200':
+ description: Successfully retrieved the compact object tree.
+ content:
+ application/json:
+ schema:
+ type: object
+ additionalProperties: true
+ example:
+ myMapKey: "my map value"
+ myCounter: 5
+ myFlag: true
+ myNestedMap:
+ nestedKey: "nested value"
+ '400':
+ description: Bad request
+ $ref: '#/components/schemas/TombstoneObjectError'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AuthError'
+ - $ref: '#/components/schemas/ClientRestrictionNotSatisfied'
+ - $ref: '#/components/schemas/InvalidTokenKey'
+ - $ref: '#/components/schemas/MalformedCredential'
+ - $ref: '#/components/schemas/OperationObjectSubscribeUnauthorized'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/ApplicationDisabled'
+ - $ref: '#/components/schemas/ChannelStateNotEnabled'
+ - $ref: '#/components/schemas/StateOperationsOnlyAppliedOnRegularChannels'
+ '404':
+ description: Not found
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AppNotFound'
+ - $ref: '#/components/schemas/ObjectNotFound'
+ '429':
+ description: Too many requests
+ $ref: '#/components/schemas/RateLimitExceeded'
+
+components:
+ schemas:
+ ApplicationDisabled:
+ title: Application disabled
+ type: object
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "Application disabled."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 403
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ nullable: true
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ nullable: true
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ AuthError:
+ title: Auth error
+ type: object
+ properties:
+ code:
+ type: integer
+ example: 401
+ message:
+ type: string
+ example: "Token expired, token revoked, or client restriction not satisfied."
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ nullable: true
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ nullable: true
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ AppNotFound:
+ title: App not found
+ type: object
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "Application not found."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 404
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ nullable: true
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ nullable: true
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ ChannelStateNotEnabled:
+ title: Channel state not enabled
+ type: object
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "Channel state is not enabled for this app."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 403
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ nullable: true
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ nullable: true
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ ClientRestrictionNotSatisfied:
+ description: Client restriction not satisfied.
+ type: object
+ properties:
+ message:
+ type: string
+ example: "Client restriction not satisfied."
+ code:
+ type: integer
+ example: 401
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 40160
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: https://help.ably.io/error/40160
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ ClientSpecifiedMessageIdInvalid:
+ type: object
+ properties:
+ message:
+ type: string
+ example: "Client-specified message ID cannot be empty or does not match the required format for batches."
+ code:
+ type: integer
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 40031
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: https://help.ably.io/error/40031
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ Counter:
+ type: object
+ properties:
+ objectId:
+ $ref: '#/components/schemas/ObjectId'
+ data:
+ type: object
+ properties:
+ number:
+ type: number
+ example: 5
+ CreateOrUpdateObjectsBadRequest:
+ oneOf:
+ - $ref: '#/components/schemas/FailureDecodingRequestBody'
+ - $ref: '#/components/schemas/InvalidObjectId'
+ - $ref: '#/components/schemas/InvalidObjectMessage'
+ - $ref: '#/components/schemas/NoOperationsInRequest'
+ - $ref: '#/components/schemas/ObjectIdOrPathRequired'
+ - $ref: '#/components/schemas/UnknownOperationType'
+ - $ref: '#/components/schemas/OperationPathNotProcessable'
+ - $ref: '#/components/schemas/ClientSpecifiedMessageIdInvalid'
+ - $ref: '#/components/schemas/FailedToUnmarshalOperationData'
+ - $ref: '#/components/schemas/NoObjectsMatchedPath'
+ - $ref: '#/components/schemas/UnableToUnquoteJsonData'
+ - $ref: '#/components/schemas/ObjectsLimitExceeded'
+ discriminator:
+ propertyName: statusCode
+ mapping:
+ 'Failure decoding request body': '#/components/schemas/FailureDecodingRequestBody'
+ 'Failed to unmarshal operation data': '#/components/schemas/FailedToUnmarshalOperationData'
+ 'No operations in request': '#/components/schemas/NoOperationsInRequest'
+ 'Operation Id or path required': '#/components/schemas/ObjectIdOrPathRequired'
+ 'No objects matched path': '#/components/schemas/NoObjectsMatchedPath'
+ 'Unknown operation type': '#/components/schemas/UnknownOperationType'
+ 'Unable to unquote json data': '#/components/schemas/UnableToUnquoteJsonData'
+ 'Invalid object message': '#/components/schemas/InvalidObjectMessage'
+ 'Invalid object id': '#/components/schemas/InvalidObjectId'
+ 'Client specified message Id invalid': '#/components/schemas/ClientSpecifiedMessageIdInvalid'
+ 'Objects limit exceeded': '#/components/schemas/ObjectsLimitExceeded'
+ 'Operation path not processable': '#/components/schemas/OperationPathNotProcessable'
+ FailureDecodingRequestBody:
+ type: object
+ additionalProperties: false
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "Failure decoding request body."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92001
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: "https://help.ably.io/error/92001"
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ FailedToUnmarshalOperationData:
+ type: object
+ additionalProperties: false
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "Failed to unmarshal operation data."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92002
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: "https://help.ably.io/error/92002"
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ InvalidObjectMessage:
+ type: object
+ properties:
+ message:
+ type: string
+ example: "Invalid object message."
+ code:
+ type: integer
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92000
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: https://help.ably.io/error/92000
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ InvalidObjectId:
+ type: object
+ properties:
+ message:
+ type: string
+ example: "Invalid object ID."
+ code:
+ type: integer
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92000
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: https://help.ably.io/error/92000
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ InvalidTokenKey:
+ title: Token expired or revoked, or key revoked, expired, or removed.
+ type: object
+ properties:
+ code:
+ type: integer
+ example: 401
+ message:
+ type: string
+ example: "Token expired"
+ MalformedCredential:
+ title: Malformed credential or invalid request.
+ type: object
+ properties:
+ code:
+ type: integer
+ example: 401
+ message:
+ type: string
+ example: "Malformed credential or unknown operation type."
+ Map:
+ type: object
+ properties:
+ entries:
+ type: object
+ additionalProperties:
+ type: object
+ properties:
+ data:
+ type: object
+ oneOf:
+ - properties:
+ string:
+ type: string
+ example: "my map value"
+ - properties:
+ objectId:
+ $ref: '#/components/schemas/ObjectId'
+ NoObjectsMatchedPath:
+ type: object
+ additionalProperties: false
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "(92005) No objects matched path."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92005
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: "https://help.ably.io/error/92005"
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ NoOperationsInRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "No operations in request."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92003
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: "https://help.ably.io/error/92003"
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ ObjectId:
+ type: string
+ example: "map:YIffJYRAP2k2e7ZP+xzequ9c5kDu1LfI/sEOKoWHvv4@1742479683000"
+ ObjectIdOrPathRequired:
+ type: object
+ additionalProperties: false
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "(92006) Object ID or path required."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92006
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: "https://help.ably.io/error/92006"
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ ObjectNotFound:
+ title: Object not found
+ type: object
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "Object not found."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 404
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92004
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: "https://help.ably.io/error/92004"
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ ObjectsLimitExceeded:
+ type: object
+ properties:
+ message:
+ type: string
+ example: "Objects limit max number exceeded."
+ code:
+ type: integer
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 32001
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: https://help.ably.io/error/32001
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ ObjectWithDetails:
+ type: object
+ required:
+ - objectId
+ properties:
+ objectId:
+ $ref: '#/components/schemas/ObjectId'
+ map:
+ $ref: '#/components/schemas/Map'
+ counter:
+ $ref: '#/components/schemas/Counter'
+ OperationPathNotProcessable:
+ type: object
+ properties:
+ message:
+ type: string
+ example: "Operation path not processable."
+ code:
+ type: integer
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92007
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: https://help.ably.io/error/92007
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ OperationObjectSubscribeUnauthorized:
+ title: Operation object-subscribe unauthorized on channel.
+ type: object
+ properties:
+ message:
+ type: string
+ example: "Operation object-subscribe unauthorized on channel."
+ code:
+ type: integer
+ example: 401
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 40160
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: https://help.ably.io/error/40160
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ RateLimitExceeded:
+ description: Rate limit exceeded.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ code:
+ type: integer
+ example: 429
+ message:
+ type: string
+ example: "Rate limit exceeded."
+ StateOperationsOnlyAppliedOnRegularChannels:
+ title: State operations only applied on regular channels
+ type: object
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "State operations only applied on regular channels (e.g., not [meta], [chat], etc channels)."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 403
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ nullable: true
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ nullable: true
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ TombstoneObjectError:
+ type: object
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "Unable to fetch objects tree for tombstone object."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92003
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: "https://help.ably.io/error/92003"
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ UnknownOperationType:
+ type: object
+ additionalProperties: false
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: "Unknown operation type."
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ example: 92007
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ example: "https://help.ably.io/error/92007"
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ UnableToUnquoteJsonData:
+ type: object
+ properties:
+ message:
+ type: string
+ example: "Unable to unquote JSON encoded data field."
+ code:
+ type: integer
+ example: 400
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ nullable: true
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ nullable: true
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ error:
+ type: object
+ additionalProperties: false
+ properties:
+ message:
+ type: string
+ description: The error message.
+ code:
+ type: integer
+ description: The HTTP status code returned.
+ statusCode:
+ type: integer
+ description: The Ably error code.
+ href:
+ type: string
+ description: The URL to documentation about the error code.
+ details:
+ type: object
+ nullable: true
+ description: Any additional details about the error message.
+ required:
+ - message
+ - code
+ - statusCode
+ - href
+ parameters:
+ idempotentParameter:
+ name: id
+ in: query
+ required: false
+ description: |
+ Optional: Only use when requiring idempotent publishing. All operations support an id field. Operations are deduplicated using idempotent publishing based on the operation's `id` field.
+
+ Batches of operations can be made idempotent using a compound key based on the format `:`. The index is the order of the operation in the batch. The base of the `id` must be the same across all operations in the batch.
+ pathParameter:
+ name: path
+ in: query
+ required: false
+ description: |
+ Optional: Only use when using path operations. Path operations issue operations against objects based on their referenced location in the tree of objects stored on the channel.
+
+ Paths must start with a key in the 'root' map, and then reference keys in nested maps, until the target location.
+
+ You can issue a single operation against multiple objects at once using the wildcard `*`.
+
+ Wildcards can be included at the end or in the middle of paths and will match exactly one level in the object tree.
+ schema:
+ type: string
+ example: "reactions.likes"
+ operationParameter:
+ name: operation
+ in: query
+ required: true
+ description: |
+ Specifies the operation to perform. Supported values include:
+ - `MAP_CREATE`: Create a map object.
+ - `MAP_SET`: Set a key-value pair in a map object.
+ - `MAP_REMOVE`: Remove a key-value pair from a map object.
+ - `COUNTER_CREATE`: Create a counter object initialized to a specific value.
+ - `COUNTER_INC`: Increment a counter object by a specified value (can be negative).
+ schema:
+ type: string
+ enum:
+ - MAP_CREATE
+ - MAP_SET
+ - MAP_REMOVE
+ - COUNTER_CREATE
+ - COUNTER_INC
+ example: COUNTER_INC
+ dataParameter:
+ name: data
+ in: query
+ required: true
+ description: |
+ The data to be created or updated. The structure depends on the operation being performed. For example, `{ "data": { "number": 4 }}`.
+
+ Maps are made of entries, which are a user-defined key (e.g., `myMapKey`) and a data value. Counters are data values containing a number.
+ schema:
+ type: object
+ additionalProperties:
+ type: object
+ properties:
+ number:
+ type: number
+ description: For COUNTER_CREATE or COUNTER_INC, this is the value.
+ example: 3
+ string:
+ type: string
+ description: A string value.
+ example: "Ably Pub/Sub"
+ boolean:
+ type: boolean
+ description: A boolean value.
+ example: true
+ bytes:
+ type: string
+ description: A base64-encoded string.
+ example: "TGl2ZU9iamVjdHMgaXMgYXdlc29tZQo="
+ encoding:
+ type: string
+ description: The encoding type for the bytes field.
+ example: "base64"
+ objectId:
+ type: string
+ description: A reference to another object.
+ example: "counter:Nz1ZiNjqsDfkDjA61xarinqpWsqEGAAw2mzWWtvX2b8@1742481614000"