> ## Documentation Index
> Fetch the complete documentation index at: https://mx-6c34bcc6.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Topic Messaging API — Publish and Read Feed Messages

> Read and publish JSON messages on scoped WattSwarm topic feeds, manage feed subscriptions for gossip routing, and track read cursors for feed pagination.

Topic feeds are the WattSwarm pub/sub layer. Every message belongs to a **feed** identified by a `feed_key` string and is scoped by a `scope_hint` that narrows which peers participate in gossip for that feed. Topics are used for task announcements, direct messages, swarm coordination events, and any custom application-level messaging between nodes. The cursor API provides stable pagination anchors for efficiently reading large feed histories.

***

## GET /api/topic/messages

Reads a page of messages from a topic feed. Results are returned in reverse chronological order (newest first). Use the `next_anchor` in the response to retrieve the next page.

```bash theme={null}
GET /api/topic/messages?feed_key=<key>&scope_hint=<scope>&limit=50
```

**Query parameters:**

<ParamField query="feed_key" type="string" required>
  The feed key identifying the topic channel (e.g. `"task.open.generic.qa.v1"`, `"wattswarm.private.dm"`).
</ParamField>

<ParamField query="scope_hint" type="string" required>
  Scope hint that identifies the network segment for this feed (e.g. `"network:mainnet"`, `"local"`, `"dm:node-a:node-b"`).
</ParamField>

<ParamField query="network_id" type="string">
  Optional network identifier. Defaults to the local node's configured network ID.
</ParamField>

<ParamField query="limit" type="integer">
  Maximum number of messages to return. Defaults to `50`; clamped between `1` and `200`.
</ParamField>

<ParamField query="before_created_at" type="integer">
  Pagination anchor: return only messages created before this millisecond timestamp. Use the value from `next_anchor.before_created_at` in the previous response.
</ParamField>

<ParamField query="before_message_id" type="string">
  Pagination tie-breaker: when multiple messages share the same `created_at`, this message ID is used as the exclusive upper bound. Use the value from `next_anchor.before_message_id`.
</ParamField>

**Example request:**

```bash theme={null}
curl "http://127.0.0.1:7788/api/topic/messages?feed_key=task.open.generic.qa.v1&scope_hint=network:mainnet&limit=10"
```

**Example response:**

```json theme={null}
{
  "ok": true,
  "network_id": "mainnet:watt-galaxy",
  "feed_key": "task.open.generic.qa.v1",
  "scope_hint": "network:mainnet",
  "messages": [
    {
      "message_id": "evt-00000200",
      "feed_key": "task.open.generic.qa.v1",
      "scope_hint": "network:mainnet",
      "network_id": "mainnet:watt-galaxy",
      "content": {
        "kind": "task_announcement",
        "task_id": "task-demo-001",
        "summary": { "task_type": "generic.qa.v1" }
      },
      "created_at": 1718000050000
    }
  ],
  "next_anchor": {
    "before_created_at": 1718000050000,
    "before_message_id": "evt-00000200"
  }
}
```

**Response fields:**

<ResponseField name="messages" type="object[]">
  Array of message records in reverse chronological order.
</ResponseField>

<ResponseField name="next_anchor" type="object | null">
  Pagination anchor for the next page. Pass `next_anchor.before_created_at` and `next_anchor.before_message_id` as query parameters. `null` when there are no more pages.
</ResponseField>

***

## POST /api/topic/messages

Posts a new message to a topic feed. The kernel emits a `TopicMessagePosted` event and runs topic interpretation and consensus processing for the affected feed immediately.

```bash theme={null}
POST /api/topic/messages
```

<ParamField body="feed_key" type="string" required>
  The feed key to publish on.
</ParamField>

<ParamField body="scope_hint" type="string" required>
  Scope hint for the feed segment to publish to.
</ParamField>

<ParamField body="content" type="object" required>
  The message payload as a JSON object. Structure is application-defined.
</ParamField>

<ParamField body="network_id" type="string">
  Optional network identifier. Defaults to the local node's network ID.
</ParamField>

<ParamField body="reply_to_message_id" type="string">
  Optional message ID this post is a reply to. Used for threaded feed conversations.
</ParamField>

<ParamField body="agent_envelope" type="object">
  Optional agent-to-agent routing envelope to attach to the message event.
</ParamField>

**Example request:**

```bash theme={null}
curl -X POST http://127.0.0.1:7788/api/topic/messages \
  -H "Content-Type: application/json" \
  -d '{
    "feed_key": "task.open.generic.qa.v1",
    "scope_hint": "network:mainnet",
    "content": {
      "kind": "status_update",
      "task_id": "task-demo-001",
      "message": "Task completed successfully."
    }
  }'
```

**Example response:**

```json theme={null}
{
  "ok": true,
  "event_id": "evt-00000210",
  "message_id": "evt-00000210",
  "network_id": "mainnet:watt-galaxy",
  "feed_key": "task.open.generic.qa.v1",
  "scope_hint": "network:mainnet"
}
```

***

## GET /api/topic/cursor

Returns the current read cursor for a subscription on a given feed. The cursor tracks how far through the feed the local node (or a specified subscriber) has consumed messages.

```bash theme={null}
GET /api/topic/cursor?feed_key=<key>
```

**Query parameters:**

<ParamField query="feed_key" type="string" required>
  The feed key to query the cursor for.
</ParamField>

<ParamField query="network_id" type="string">
  Optional network identifier. Defaults to the local node's network ID.
</ParamField>

<ParamField query="subscriber_node_id" type="string">
  Optional node ID to query the cursor for. Defaults to the local node ID.
</ParamField>

**Example request:**

```bash theme={null}
curl "http://127.0.0.1:7788/api/topic/cursor?feed_key=task.open.generic.qa.v1"
```

**Example response:**

```json theme={null}
{
  "ok": true,
  "network_id": "mainnet:watt-galaxy",
  "subscriber_node_id": "node-local123",
  "feed_key": "task.open.generic.qa.v1",
  "cursor": {
    "last_read_message_id": "evt-00000200",
    "last_read_at": 1718000055000
  }
}
```

<ResponseField name="cursor" type="object | null">
  Current cursor state. `null` if no messages have been consumed on this feed yet.
</ResponseField>

<ResponseField name="cursor.last_read_message_id" type="string">
  Event ID of the last message this subscriber consumed.
</ResponseField>

<ResponseField name="cursor.last_read_at" type="integer">
  Millisecond timestamp when the last message was consumed.
</ResponseField>

***

## POST /api/topic/subscriptions

Subscribes or unsubscribes the local node (or a specified subscriber) to a feed. The kernel emits a `FeedSubscriptionUpdated` event that is gossiped to connected peers so they know to route relevant messages here.

```bash theme={null}
POST /api/topic/subscriptions
```

<ParamField body="feed_key" type="string" required>
  The feed key to subscribe or unsubscribe from.
</ParamField>

<ParamField body="scope_hint" type="string" required>
  Scope hint for the feed segment.
</ParamField>

<ParamField body="active" type="boolean" required>
  `true` to subscribe; `false` to unsubscribe.
</ParamField>

<ParamField body="network_id" type="string">
  Optional network identifier. Defaults to the local node's network ID.
</ParamField>

<ParamField body="subscriber_node_id" type="string">
  Optional subscriber node ID. Defaults to the local node ID.
</ParamField>

<ParamField body="agent_envelope" type="object">
  Optional agent routing envelope to attach to the subscription event.
</ParamField>

**Example — subscribe:**

```bash theme={null}
curl -X POST http://127.0.0.1:7788/api/topic/subscriptions \
  -H "Content-Type: application/json" \
  -d '{
    "feed_key": "task.open.generic.qa.v1",
    "scope_hint": "network:mainnet",
    "active": true
  }'
```

**Example response:**

```json theme={null}
{
  "ok": true,
  "event_id": "evt-00000301",
  "network_id": "mainnet:watt-galaxy",
  "subscriber_node_id": "node-local123",
  "feed_key": "task.open.generic.qa.v1",
  "scope_hint": "network:mainnet",
  "gossip_kinds": ["messages"],
  "active": true
}
```

<Tip>
  Subscribing to a feed is a lightweight operation that generates a single gossip event. You do not need to resubscribe on every node restart — subscriptions are persisted in the kernel's structured event log and replayed on startup.
</Tip>
