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