diff --git a/src/pages/docs/api/realtime-sdk/channels.mdx b/src/pages/docs/api/realtime-sdk/channels.mdx
index 163fafb434..3756535776 100644
--- a/src/pages/docs/api/realtime-sdk/channels.mdx
+++ b/src/pages/docs/api/realtime-sdk/channels.mdx
@@ -106,6 +106,14 @@ Provides access to the [Presence](/docs/presence-occupancy/presence) object for
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.
+
+
+#### annotations
+
+Provides access to the [`RealtimeAnnotations`](#realtime-annotations) object for this channel, which can be used to publish, delete, retrieve and subscribe to [annotations](/docs/messages/annotations) on messages.
+
+
+
#### object
@@ -1763,6 +1771,234 @@ Checks if a channel is in a given state and returns `null` if it is, else calls
Returns a promise. If the channel is already in the given state, the promise will immediately resolve to `null`. If not, it will call [`once()`](#once) to return a promise which resolves the next time the channel transitions to the given state.
+
+
+## RealtimeAnnotationsARTRealtimeAnnotationsio.ably.lib.realtime.RealtimeAnnotationsRealtimeAnnotations
+
+The `RealtimeAnnotations` object, accessed from the [annotations](#channel-annotations) property of a `Channel`, is used to publish, delete, retrieve, and subscribe to [annotations](/docs/messages/annotations) on messages. See the [annotations documentation](/docs/messages/annotations) for an overview of annotation concepts and types.
+
+### RealtimeAnnotations Methods
+
+#### publish
+
+
+
+`publish(Message message, Annotation annotation): Promise`
+
+`publish(String messageSerial, Annotation annotation): Promise`
+
+
+
+
+`void publish(Message message, Annotation annotation)`
+
+`void publish(String messageSerial, Annotation annotation)`
+
+`void publish(Message message, Annotation annotation, CompletionListener listener)`
+
+`void publish(String messageSerial, Annotation annotation, CompletionListener listener)`
+
+
+
+
+`publishForMessage(message: ARTMessage, annotation: ARTAnnotation, callback: ARTCallback?)`
+
+`publishForMessageSerial(messageSerial: String, annotation: ARTAnnotation, callback: ARTCallback?)`
+
+
+
+
+`async publish(msg_or_serial, annotation: Annotation)`
+
+
+
+Publishes an [`Annotation`](/docs/api/realtime-sdk/types#annotation) for a message, identified either by a [`Message`](#message) instance or by its [`serial`](#serial). The annotation must include at least a `type`; other required fields depend on the annotation type. The `action` is set automatically to `annotation.create`.
+
+If a [`clientId`](/docs/api/realtime-sdk/types#client-options) is set on the client, it will be associated with the published annotation. Some annotation [summarization methods](/docs/messages/annotations#annotation-types) require an [identified](/docs/auth/identified-clients) client.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| msg_or_serialmessage or messageSerial | The message to annotate, identified either by a [`Message`](#message) instance or by its `serial` | [`ARTMessage`](#message) or `String`[`Message`](#message) or `String` |
+| annotation | The annotation to publish. Must include at least the `type` | [`ARTAnnotation`](/docs/api/realtime-sdk/types#annotation)[`Annotation`](/docs/api/realtime-sdk/types#annotation) |
+
+#### delete
+
+
+
+`delete(Message message, Annotation annotation): Promise`
+
+`delete(String messageSerial, Annotation annotation): Promise`
+
+
+
+
+`void delete(Message message, Annotation annotation)`
+
+`void delete(String messageSerial, Annotation annotation)`
+
+`void delete(Message message, Annotation annotation, CompletionListener listener)`
+
+`void delete(String messageSerial, Annotation annotation, CompletionListener listener)`
+
+
+
+
+`deleteForMessage(message: ARTMessage, annotation: ARTAnnotation, callback: ARTCallback?)`
+
+`deleteForMessageSerial(messageSerial: String, annotation: ARTAnnotation, callback: ARTCallback?)`
+
+
+
+
+`async delete(msg_or_serial, annotation: Annotation)`
+
+
+
+Publishes an annotation removal request for a message, removing the contribution that this `clientId` previously made to the [annotation summary](/docs/messages/annotations#annotation-summaries). The exact behaviour depends on the [annotation type](/docs/messages/annotations#annotation-types). The `action` is set automatically to `annotation.delete`.
+
+The annotation must include at least a `type`, and may include a `name` for those annotation types where the name is needed to identify which contribution to remove.
+
+##### Parameters
+
+The parameters are the same as for [`publish`](#annotations-publish).
+
+#### get
+
+
+
+`get(Message message, GetAnnotationsParams? params): Promise>`
+
+`get(String messageSerial, GetAnnotationsParams? params): Promise>`
+
+
+
+
+`PaginatedResult get(Message message, Param[] params)`
+
+`PaginatedResult get(String messageSerial, Param[] params)`
+
+`PaginatedResult get(Message message)`
+
+`PaginatedResult get(String messageSerial)`
+
+`void getAsync(Message message, Param[] params, Callback> callback)`
+
+`void getAsync(String messageSerial, Param[] params, Callback> callback)`
+
+
+
+
+`getForMessage(message: ARTMessage, query: ARTAnnotationsQuery, callback: ARTPaginatedAnnotationsCallback)`
+
+`getForMessageSerial(messageSerial: String, query: ARTAnnotationsQuery, callback: ARTPaginatedAnnotationsCallback)`
+
+
+
+
+`async get(msg_or_serial, params: dict | None = None) -> PaginatedResult`
+
+
+
+Retrieves a [paginated result](#paginated-result) containing all individual [`Annotation`](/docs/api/realtime-sdk/types#annotation) events that have been published for a message, ordered from earliest to most recent.
+
+If you only need the latest [annotation summary](/docs/messages/annotations#annotation-summaries) for a message, prefer [`channel.getMessage()`](/docs/messages/updates-deletes#get): the returned message contains the up-to-date `annotations.summary`. Use `annotations.get()` only when you need the full list of raw annotations.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| msg_or_serialmessage or messageSerial | The message to retrieve annotations for, identified either by a [`Message`](#message) instance or by its `serial` | [`ARTMessage`](#message) or `String`[`Message`](#message) or `String` |
+| queryparams | An optional set of restrictions on which annotations to retrieve, in particular a `limit` | [`GetAnnotationsParams`](/docs/api/realtime-sdk/types#get-annotations-params)[`Param[]`](#param)[`ARTAnnotationsQuery`](/docs/api/realtime-sdk/types#annotations-query)`dict` |
+
+##### Returns
+
+A [`PaginatedResult`](#paginated-result) containing an array of [`Annotation`](/docs/api/realtime-sdk/types#annotation) objects.
+
+#### subscribe
+
+
+
+`subscribe(messageCallback listener): Promise`
+
+`subscribe(String | String[] type, messageCallback listener): Promise`
+
+
+
+
+`void subscribe(AnnotationListener listener)`
+
+`void subscribe(String type, AnnotationListener listener)`
+
+
+
+
+`subscribe(callback: (ARTAnnotation) -> Void) -> ARTEventListener?`
+
+`subscribe(type: String, callback: (ARTAnnotation) -> Void) -> ARTEventListener?`
+
+
+
+
+`async subscribe(listener)`
+
+`async subscribe(type_or_types, listener)`
+
+
+
+Registers a listener that is called each time an individual [`Annotation`](/docs/api/realtime-sdk/types#annotation) is received on the channel.
+
+To receive individual realtime annotations (instead of just the rolled-up [summaries](/docs/messages/annotations#annotation-summaries)), the channel must have requested the `ANNOTATION_SUBSCRIBE` [channel mode](/docs/channels/options#modes). Most clients will not need this and can rely on the summary updates instead.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| type | An optional annotation `type` (or array of types) to filter the listener on. If omitted, the listener is called for all annotations on the channel | `String` or `String[]` |
+| listener | A function called with each received annotation | `(annotation: Annotation) => void``AnnotationListener``(ARTAnnotation) -> Void``callable` |
+
+#### unsubscribe
+
+
+
+`unsubscribe(): void`
+
+`unsubscribe(messageCallback listener): void`
+
+`unsubscribe(String | String[] type, messageCallback listener): void`
+
+
+
+
+`void unsubscribe(AnnotationListener listener)`
+
+`void unsubscribe(String type, AnnotationListener listener)`
+
+
+
+
+`unsubscribe()`
+
+`unsubscribe(listener: ARTEventListener)`
+
+`unsubscribe(type: String, listener: ARTEventListener)`
+
+
+
+
+`unsubscribe()`
+
+`unsubscribe(listener)`
+
+`unsubscribe(type_or_types, listener)`
+
+
+
+Deregisters annotation listeners. Called with no arguments, removes all listeners. Called with a `listener`, removes that listener from all types it was registered for. Called with a `type` and a `listener`, removes the listener from that type only.
+
+
+
## Related types
### ChannelStateARTRealtimeChannelStateChannel::STATE Enumio.ably.lib.realtime.ChannelState EnumIO.Ably.Realtime.ChannelState Enumably.ChannelState Enum
diff --git a/src/pages/docs/api/realtime-sdk/types.mdx b/src/pages/docs/api/realtime-sdk/types.mdx
index c762b5ed16..5017487d97 100644
--- a/src/pages/docs/api/realtime-sdk/types.mdx
+++ b/src/pages/docs/api/realtime-sdk/types.mdx
@@ -583,7 +583,227 @@ An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects
| Property | Description | Type |
|----------|-------------|------|
-| summary | An object whose keys are annotation types, and the values are aggregated summaries for that annotation type | `Record` |
+| summary | An object whose keys are [annotation types](/docs/messages/annotations#annotation-types), and whose values are aggregated [summary entries](#summary-entry)summary entries for that annotation type. The structure of each value depends on the summarization method, for example a `total.v1` entry will have a `total` field, while a `flag.v1` entry will have `total` and `clientIds` fields. See [annotation summaries](/docs/messages/annotations#annotation-summaries) for details | `Record`[`Summary`](#summary)`NSDictionary``Dict[str, Dict]``Record` |
+
+
+
+### AnnotationARTAnnotationio.ably.lib.types.AnnotationAnnotation
+
+An `Annotation` represents an individual annotation event for a message — either an `annotation.create` or `annotation.delete`.
+
+The same type is used both for publishing an annotation and for receiving one. When publishing, only `type` is required; some annotation types additionally need `name` (and `count` for `multiple.v1`), and `data`, `extras`, and `encoding` are optional user-set fields. The other fields (`id`, `clientId`, `timestamp`, `serial`, `messageSerial`, `action`) are server- or SDK-populated, and are present on annotations received via [`channel.annotations.subscribe`](/docs/api/realtime-sdk/channels#annotations-subscribe) or [`channel.annotations.get`](/docs/api/realtime-sdk/channels#annotations-get). See the per-field descriptions below for details.
+
+#### PropertiesMembersAttributes
+
+| Property | Description | Type |
+|----------|-------------|------|
+| typetype | The [annotation type](/docs/messages/annotations#annotation-types) (for example `reactions:distinct.v1`). Required when publishing | `String` |
+| namename | The name of the annotation, used by some annotation types for aggregation | `String` |
+| countcount | An optional count, only relevant to the `multiple.v1` annotation type | `int``Integer``Integer``NSNumber` |
+| datadata | An optional payload for the annotation. Available on individual annotations but not aggregated or included in [annotation summaries](/docs/messages/annotations#annotation-summaries) | `Any``Object``Any``Any` |
+| extrasextras | Metadata and/or ancillary payloads, if provided. Valid payloads include [`push`](/docs/push/publish#payload), `headers` (a map of strings to strings for arbitrary customer-supplied metadata), [`ephemeral`](/docs/pub-sub/advanced#ephemeral), and [`privileged`](/docs/platform/integrations/skip-integrations) objects | `JSONObject`, `JSONArray``JSON Object``Dictionary`, `Array``NSDictionary *`, `NSArray *` |
+| idid | A unique ID assigned by Ably to this annotation. Server-set | `String` |
+| clientIdclient_id | The client ID of the publisher of this annotation. Server-set, from the publishing client's connection | `String` |
+| timestamptimestamp | The timestamp of when the annotation was received by Ably, as milliseconds since the Unix epoch. Server-set | `Integer``Long Integer``NSDate` |
+| actionaction | Whether this is an annotation being added or removed. Set automatically by the SDK when publishing or deleting | [`AnnotationAction`](#annotation-action) |
+| serialserial | This annotation's unique serial (lexicographically totally ordered). Server-set | `String` |
+| messageSerialmessageSerialmessage_serial | The serial of the message that this annotation is annotating. Set automatically by the SDK from the [`Message`](#message) or `messageSerial` argument passed to publish/delete | `String` |
+| encodingencoding | This is typically empty, as all annotations received from Ably are automatically decoded client-side using this value. However, if the annotation encoding cannot be processed, this attribute contains the remaining transformations not applied to the `data` payload | `String` |
+
+
+
+
+
+### AnnotationActionARTAnnotationActionio.ably.lib.types.AnnotationActionAnnotationAction
+
+An enum identifying whether an annotation event is a creation or a deletion.
+
+
+
+
+```javascript
+ const AnnotationActions = [
+ 'annotation.create',
+ 'annotation.delete',
+ ]
+```
+
+
+
+
+
+
+```nodejs
+ const AnnotationActions = [
+ 'annotation.create',
+ 'annotation.delete',
+ ]
+```
+
+
+
+
+
+
+```java
+ public enum AnnotationAction {
+ ANNOTATION_CREATE, // 0
+ ANNOTATION_DELETE, // 1
+ }
+```
+
+
+
+
+
+
+```objc
+ typedef NS_ENUM(NSUInteger, ARTAnnotationAction) {
+ ARTAnnotationActionCreate,
+ ARTAnnotationActionDelete,
+ };
+```
+
+
+
+
+
+
+```swift
+ enum ARTAnnotationAction : UInt {
+ case create
+ case delete
+ }
+```
+
+
+
+
+
+
+```python
+ class AnnotationAction(Enum):
+ ANNOTATION_CREATE = 'annotation.create'
+ ANNOTATION_DELETE = 'annotation.delete'
+```
+
+
+
+
+
+
+
+
+### GetAnnotationsParams
+
+The parameters object accepted by [`channel.annotations.get`](/docs/api/realtime-sdk/channels#annotations-get).
+
+| Property | Description | Type |
+|----------|-------------|------|
+| limit | An upper limit on the number of annotations returned. The default is 100, and the maximum is 1000 | `Integer` |
+
+
+
+
+
+### ARTAnnotationsQuery
+
+The query object accepted by [`channel.annotations.get`](/docs/api/realtime-sdk/channels#annotations-get). Constructed via `ARTAnnotationsQuery(limit:)`.
+
+| Property | Description | Type |
+|----------|-------------|------|
+| limit | An upper limit on the number of annotations returned. The default is 100, and the maximum is 1000 | `NSUInteger` |
+
+
+
+
+
+### Summary entry types
+
+The values held in [`MessageAnnotations.summary`](#message-annotations) depend on the [summarization method](/docs/messages/annotations#annotation-types) of the annotation type. The shape of each entry corresponds to one of the following.
+
+
+
+The TypeScript type [`SummaryEntry`](#summary-entry) is a union of these shapes.
+
+
+
+
+In Java, [`MessageAnnotations.summary`](#message-annotations) is exposed as a [`Summary`](#summary) object that holds the raw `JsonObject` for each annotation type. To extract a strongly-typed entry from the [`Summary`](#summary), use one of the static converters on the `Summary` class:
+
+| Converter | Returns |
+|-----------|---------|
+| `Summary.asSummaryDistinctV1(JsonObject)` | `Map` |
+| `Summary.asSummaryUniqueV1(JsonObject)` | `Map` |
+| `Summary.asSummaryMultipleV1(JsonObject)` | `Map` |
+| `Summary.asSummaryFlagV1(JsonObject)` | [`SummaryClientIdList`](#summary-client-id-list) |
+| `Summary.asSummaryTotalV1(JsonObject)` | [`SummaryTotal`](#summary-total) |
+
+
+
+
+In Objective-C and Swift, [`MessageAnnotations.summary`](#message-annotations) is exposed as a loose `NSDictionary`. To extract a strongly-typed entry, use one of the global parser functions:
+
+| Parser | Returns |
+|--------|---------|
+| `ARTSummaryDistinctV1(NSDictionary)` | `NSDictionary` |
+| `ARTSummaryUniqueV1(NSDictionary)` | `NSDictionary` |
+| `ARTSummaryMultipleV1(NSDictionary)` | `NSDictionary` |
+| `ARTSummaryFlagV1(NSDictionary)` | [`ARTSummaryClientIdList`](#summary-client-id-list) |
+| `ARTSummaryTotalV1(NSDictionary)` | [`ARTSummaryTotal`](#summary-total) |
+
+
+
+
+In Python the summary is a plain `dict`. The shapes below describe the structure rather than dedicated classes.
+
+
+
+#### SummaryClientIdListARTSummaryClientIdListio.ably.lib.types.SummaryClientIdListflag.v1 entry
+
+The summary entry for `flag.v1`, and the per-name value for `distinct.v1` and `unique.v1`.
+
+| Property | Description | Type |
+|----------|-------------|------|
+| total | The total number of clients who have published an annotation with this name (or type, depending on context) | `Integer``int``NSInteger` |
+| clientIds | A list of the clientIds of all clients who have published an annotation with this name (or type, depending on context) | `String[]``List``NSArray``List[str]` |
+| clipped | Whether the list of clientIds has been clipped due to exceeding the maximum number of clients | `Boolean``boolean``BOOL` |
+
+#### SummaryClientIdCountsARTSummaryClientIdCountsio.ably.lib.types.SummaryClientIdCountsmultiple.v1 entry
+
+The per-name value for the `multiple.v1` annotation type.
+
+| Property | Description | Type |
+|----------|-------------|------|
+| total | The sum of the counts from all clients who have published an annotation with this name | `Integer``int``NSInteger` |
+| clientIds | A map from clientIds to the count each of those clients has contributed | `Record``Map``NSDictionary``Dict[str, int]` |
+| totalUnidentifiedtotalUnidentified | The sum of counts contributed by unidentified clients (which are not included in the `clientIds` map) | `Integer``int``NSInteger` |
+| clipped | Whether the `clientIds` map has been clipped due to exceeding the maximum number of clients | `Boolean``boolean``BOOL` |
+| totalClientIdstotalClientIds | The total number of distinct identified clients (equal to the size of `clientIds` if `clipped` is false) | `Integer``int``NSInteger` |
+
+#### SummaryTotalARTSummaryTotalio.ably.lib.types.SummaryTotaltotal.v1 entry
+
+The summary entry for the `total.v1` annotation type.
+
+| Property | Description | Type |
+|----------|-------------|------|
+| total | The total number of annotations of this type that have been published for the message | `Integer``int``NSInteger` |
+
+
+
+#### io.ably.lib.types.Summary
+
+A wrapper around the loose `Map` representation of [`MessageAnnotations.summary`](#message-annotations).
+
+| Member | Description | Type |
+|--------|-------------|------|
+| `JsonObject get(String annotationType)` | Returns the raw JSON for the given annotation type, or `null` | `JsonObject` |
+
+Use the static converter methods listed [above](#summary-entry) (`Summary.asSummaryDistinctV1`, `Summary.asSummaryFlagV1`, etc.) to turn a `JsonObject` into a strongly-typed entry.
+
+
+
+
### PublishResult
diff --git a/src/pages/docs/api/rest-sdk/channels.mdx b/src/pages/docs/api/rest-sdk/channels.mdx
index abe67b05d1..bea30de1d4 100644
--- a/src/pages/docs/api/rest-sdk/channels.mdx
+++ b/src/pages/docs/api/rest-sdk/channels.mdx
@@ -86,6 +86,14 @@ Provides access to the [REST Presence](/docs/presence-occupancy/presence) object
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.
+
+
+#### annotations
+
+Provides access to the [`RestAnnotations`](#rest-annotations) object for this channel, which can be used to publish, delete, and retrieve [annotations](/docs/messages/annotations) on messages.
+
+
+
#### object
@@ -813,6 +821,147 @@ On failure to retrieve message versions, the callback receives an [`ARTErrorInfo
+
+
+## RestAnnotationsARTRestAnnotationsio.ably.lib.rest.RestAnnotationsRestAnnotations
+
+The `RestAnnotations` object, accessed from the [annotations](#channel-annotations) property of a `Channel`, is used to publish, delete, and retrieve [annotations](/docs/messages/annotations) on messages over REST. See the [annotations documentation](/docs/messages/annotations) for an overview of annotation concepts and types.
+
+### RestAnnotations Methods
+
+#### publish
+
+
+
+`publish(Message message, Annotation annotation): Promise`
+
+`publish(String messageSerial, Annotation annotation): Promise`
+
+
+
+
+`void publish(Message message, Annotation annotation)`
+
+`void publish(String messageSerial, Annotation annotation)`
+
+`void publishAsync(Message message, Annotation annotation, CompletionListener listener)`
+
+`void publishAsync(String messageSerial, Annotation annotation, CompletionListener listener)`
+
+
+
+
+`publishForMessage(message: ARTMessage, annotation: ARTAnnotation, callback: ARTCallback?)`
+
+`publishForMessageSerial(messageSerial: String, annotation: ARTAnnotation, callback: ARTCallback?)`
+
+
+
+
+`async publish(msg_or_serial, annotation: Annotation)`
+
+
+
+Publishes an [`Annotation`](/docs/api/realtime-sdk/types#annotation) for a message, identified either by a [`Message`](#message) instance or by its [`serial`](#serial). The annotation must include at least a `type`. The `action` is set automatically to `annotation.create`.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| msg_or_serialmessage or messageSerial | The message to annotate, identified either by a [`Message`](#message) instance or by its `serial` | [`ARTMessage`](#message) or `String`[`Message`](#message) or `String` |
+| annotation | The annotation to publish. Must include at least the `type` | [`ARTAnnotation`](/docs/api/realtime-sdk/types#annotation)[`Annotation`](/docs/api/realtime-sdk/types#annotation) |
+
+#### delete
+
+
+
+`delete(Message message, Annotation annotation): Promise`
+
+`delete(String messageSerial, Annotation annotation): Promise`
+
+
+
+
+`void delete(Message message, Annotation annotation)`
+
+`void delete(String messageSerial, Annotation annotation)`
+
+`void deleteAsync(Message message, Annotation annotation, CompletionListener listener)`
+
+`void deleteAsync(String messageSerial, Annotation annotation, CompletionListener listener)`
+
+
+
+
+`deleteForMessage(message: ARTMessage, annotation: ARTAnnotation, callback: ARTCallback?)`
+
+`deleteForMessageSerial(messageSerial: String, annotation: ARTAnnotation, callback: ARTCallback?)`
+
+
+
+
+`async delete(msg_or_serial, annotation: Annotation)`
+
+
+
+Publishes an annotation removal request for a message. The `action` is set automatically to `annotation.delete`. See the [annotations documentation](/docs/messages/annotations#delete) for behaviour details.
+
+##### Parameters
+
+The parameters are the same as for [`publish`](#annotations-publish).
+
+#### get
+
+
+
+`get(Message message, GetAnnotationsParams? params): Promise>`
+
+`get(String messageSerial, GetAnnotationsParams? params): Promise>`
+
+
+
+
+`PaginatedResult get(Message message, Param[] params)`
+
+`PaginatedResult get(String messageSerial, Param[] params)`
+
+`PaginatedResult get(Message message)`
+
+`PaginatedResult get(String messageSerial)`
+
+`void getAsync(Message message, Param[] params, Callback> callback)`
+
+`void getAsync(String messageSerial, Param[] params, Callback> callback)`
+
+
+
+
+`getForMessage(message: ARTMessage, query: ARTAnnotationsQuery, callback: ARTPaginatedAnnotationsCallback)`
+
+`getForMessageSerial(messageSerial: String, query: ARTAnnotationsQuery, callback: ARTPaginatedAnnotationsCallback)`
+
+
+
+
+`async get(msg_or_serial, params: dict | None = None) -> PaginatedResult`
+
+
+
+Retrieves a [paginated result](#paginated-result) containing all individual [`Annotation`](/docs/api/realtime-sdk/types#annotation) events that have been published for a message, ordered from earliest to most recent.
+
+##### Parameters
+
+| Parameter | Description | Type |
+|-----------|-------------|------|
+| msg_or_serialmessage or messageSerial | The message to retrieve annotations for, identified either by a [`Message`](#message) instance or by its `serial` | [`ARTMessage`](#message) or `String`[`Message`](#message) or `String` |
+| queryparams | An optional set of restrictions on which annotations to retrieve, in particular a `limit` | [`GetAnnotationsParams`](/docs/api/realtime-sdk/types#get-annotations-params)[`Param[]`](/docs/api/rest-sdk/types#param)[`ARTAnnotationsQuery`](/docs/api/realtime-sdk/types#annotations-query)`dict` |
+
+##### Returns
+
+A [`PaginatedResult`](#paginated-result) containing an array of [`Annotation`](/docs/api/realtime-sdk/types#annotation) objects.
+
+
+
## Related types
### MessageARTMessageAbly::Models::MessageAbly\Models\Messageio.ably.lib.types.MessageIO.Ably.Message
diff --git a/src/pages/docs/messages/annotations.mdx b/src/pages/docs/messages/annotations.mdx
index c3ab7ca093..eb453d46f1 100644
--- a/src/pages/docs/messages/annotations.mdx
+++ b/src/pages/docs/messages/annotations.mdx
@@ -505,9 +505,11 @@ await channel.annotations.delete(message.serial, annotation)
## Subscribe to annotation summaries
-The simplest way to receive annotation updates is via annotation summaries. A message that has annotations also includes a summary which is a synopsis of certain details of all annotations for that message. Summaries are updated in response to further annotation events for that message, and summary changes are delivered by default to subscribing clients.
+The simplest way to receive annotation updates is via annotation summaries. A message that has annotations also includes a summary, which is a synopsis of certain details of all annotations for that message. Summaries are updated in response to further annotation events for that message, and summary changes are delivered by default to subscribing clients.
-Annotation summaries are delivered to subscribers as messages with an `action` of `message.summary`, and a `serial` matching the `serial` of the message that they are updating. They have an `annotations` field which contains a `summary` of all the annotations for the message.
+When an already-existing message has an updated summary, the new version of that message will be delivered to subscribers with an [`action`](/docs/api/realtime-sdk/types#action) of `message.summary` (as opposed to `message.create` for the originally-published one), and a `serial` matching the `serial` of the original message. It will have an `annotations` field which contains a `summary` of all the annotations for the message.
+
+If you're not subscribing, you also retrieve the current version of a message, including the latest version of its summary, using [`channel.getMessage()`](/docs/api/rest-sdk/channels#get-message). Messages returned from [history](/docs/storage-history/history) also include the latest summary at the time the history request is made.
The value of that `summary` field is an object where the keys are the [annotation types](#annotation-types). The structure of the value of each key depends on the summarization method used, for example `total.v1` will have a `total` field, while `flag.v1` will have `total` and `clientIds` fields.
@@ -799,9 +801,75 @@ await channel.annotations.subscribe(annotation_handler)
```
-### Annotation message properties
+## Retrieve annotations
+
+To retrieve all annotations that have been published for a message, use the `annotations.get()` method on a channel. Pass in either a [message](/docs/messages) instance or the `serial` of the message. This returns a [paginated result](/docs/api/realtime-sdk/types#paginated-result) containing every individual annotation event for that message, starting from the earliest. You can pass an optional `limit` to control the page size.
+
+
+
+
+```javascript
+// Retrieve all annotations for a message
+const page = await channel.annotations.get(message, { limit: 100 });
+console.log(page.items);
+
+// You can also use a message's `serial`
+const page2 = await channel.annotations.get(message.serial, { limit: 100 });
+```
+
+```nodejs
+// Retrieve all annotations for a message
+const page = await channel.annotations.get(message, { limit: 100 });
+console.log(page.items);
+
+// You can also use a message's `serial`
+const page2 = await channel.annotations.get(message.serial, { limit: 100 });
+```
+
+```java
+// Retrieve all annotations for a message
+Param[] params = new Param[]{ new Param("limit", "100") };
+PaginatedResult page = channel.annotations.get(message, params);
+for (Annotation annotation : page.items()) {
+ System.out.println(annotation);
+}
+
+// You can also use a message's `serial`
+PaginatedResult page2 = channel.annotations.get(message.serial, params);
+```
+
+```swift
+// Retrieve all annotations for a message
+let query = ARTAnnotationsQuery(limit: 100)
+channel.annotations.get(for: message, query: query) { page, error in
+ guard let page = page else { return }
+ for annotation in page.items {
+ print(annotation)
+ }
+}
+
+// You can also use a message's `serial`
+channel.annotations.get(forMessageSerial: message.serial, query: query) { page, error in
+ // Handle result
+}
+```
+
+```python
+# Retrieve all annotations for a message
+page = await channel.annotations.get(message, {'limit': 100})
+for annotation in page.items:
+ print(annotation)
+
+# You can also use a message's `serial`
+page2 = await channel.annotations.get(message.serial, {'limit': 100})
+```
+
+
+## Annotation message properties
-Annotations are a special type of message with the following properties:
+Annotations have the following properties:
| Property | Description |
| -------- | ----------- |