# Shared Thread Presence

The slug-scoped twin of the thread presence endpoints, for a **recipient who joined a live-shared thread** (and is therefore not the thread owner or a space member). Lets the joiner heartbeat their presence — and read who is present — so "who's here / who's typing" fans out **both ways** between the owner and every joiner.

Allowed **only when the link's `access_level` is `live`** (`403` otherwise); an unknown or unauthorized link returns `404`. Presence is transient cache state — these endpoints make no database writes, and an empty present set is a valid answer.

## Heartbeat presence

Records (or refreshes) that the caller is present on the shared thread, and what they are doing. Clients ping this every ~20 seconds while the thread is open; presence auto-expires ~50 seconds after the last heartbeat.

### Authentication

Requires **user** authentication (Bearer token). API-key authentication is rejected with `403`.

### Path Parameters

**`slug`** string required

The share-link slug (must be a `live` link).

### Body Parameters

**`state`** string optional

One of `viewing` (default), `typing`, or `prompting`.

**`client_id`** string optional

A stable id for the **device or tab** sending this heartbeat. Pattern `^[A-Za-z0-9_-]+$`, max 64 characters. Generate it once per surface and reuse it for that surface's lifetime.

Send it whenever one account may have the shared thread open in more than one place — a phone and a desktop. Each device then holds its **own** presence entry, so one device's heartbeat cannot overwrite another's state and closing one does not remove the other. Presence is still reported **per user**: the snapshot collapses a user's devices into a single entry carrying their most recently active state, so the response shape is unchanged. Omit it and the caller is treated as that user's only device.

### Returns

A list of the users currently present on the underlying thread, each with their `user_id`, `state`, `name`, `avatar_url` (profile-image URL, or `null` — falls back to initials), and `last_seen` (Unix timestamp).

## Get presence snapshot

Returns the same present set on demand, without recording a heartbeat.

## Code Examples

### cURL


```bash
curl -X POST https://api.aitronos.com/v1/shared-threads/abc123slug/presence \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"state": "typing"}'
```

### Python SDK


```python
from aitronos import Aitronos

client = Aitronos(api_key="", token="YOUR_TOKEN")
present = client.threads.shared.presence(slug="abc123slug", state="typing")
print([p.user_id for p in present.data])
```

### Python (requests)


```python
import requests

response = requests.post(
    "https://api.aitronos.com/v1/shared-threads/abc123slug/presence",
    headers={"Authorization": "Bearer YOUR_TOKEN"},
    json={"state": "typing"},
)
print(response.json()["data"])
```

### JavaScript


```javascript
const response = await fetch(
  "https://api.aitronos.com/v1/shared-threads/abc123slug/presence",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_TOKEN",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ state: "typing" }),
  },
);
const present = await response.json();
console.log(present.data);
```

## Response Examples

### Success (200)


```json
{
  "object": "list",
  "thread_id": "thread_9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a",
  "data": [
    {
      "user_id": "usr_3ae598ea791242898b5feb69e4c4e30c",
      "state": "typing",
      "name": "Aitronos Team Dev",
      "avatar_url": "https://cdn.aitronos.com/avatars/usr_abc123.png",
      "last_seen": 1782681710.5
    },
    {
      "user_id": "usr_38893a99f74344e4a6cac1e99d813f74",
      "state": "viewing",
      "name": "Owner",
      "avatar_url": null,
      "last_seen": 1782681709.1
    }
  ]
}
```

### Error — Not a live link (403)


```json
{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "You do not have permission to perform this action.",
    "system_message": "This share link does not grant live co-working access",
    "type": "authorization_error",
    "status": 403,
    "details": {
      "link_id": "tslnk_2b1c3d4e5f6a7b8c9d0e1f2a3b4c5d6e",
      "access_level": "view"
    },
    "trace_id": "...",
    "timestamp": "..."
  }
}
```

## Related Resources

- [Join Shared Thread](/api-reference/shared-threads/join-shared-thread) — Create the persistent joined entry
- [Live Shared Thread](/api-reference/shared-threads/live-shared-thread) — The live co-working event stream (presence updates fan out here)
- [Leave Shared Thread](/api-reference/shared-threads/leave-shared-thread) — Remove the joined entry