Read and advance your own reading position on a shared thread, so a client can put you back where you stopped and draw an "N new messages" divider instead of always landing at the newest message.
The owner read routes are ownership-scoped and return 404 for a share-link recipient. These are the slug-scoped equivalents. Authorization is the same as reading the thread's messages — the slug must resolve for you — so both a view-only visitor and a joined participant can use them.
The marker is per-viewer: it is keyed on the authenticated user and is never shared between people. It is also the same read cursor the thread's unread badge uses, so marking read here clears the thread's unread indicator everywhere rather than keeping a separate position that drifts.
Requires user authentication (Bearer token). An API key has no recipient identity and is rejected with 403.
slug string required
The share-link slug, from the share URL.
last_read_message_id string required
The newest message you have now read. It must be a message this thread actually shows you; an unknown, deleted, hidden, or other-thread id returns 404 rather than being stored, because a marker pointing at nothing is a restore position you could never clear.
The write is monotonic: a message older than your stored marker leaves the stored one in place. Scroll-driven calls, a second tab, and late retries can therefore never rewind your progress.
Both methods return the read state as it stands after the call, so a client never has to re-read to learn the outcome.
- last_read_message_id
string | null— the message you last read.nullis normal: you have never opened the thread, or your cursor was set by a whole-thread mark-read that named no message. - last_read_at
string | null— timestamp of the cursor. Set whenever any read state exists, even whenlast_read_message_idisnull. - unread_count
integer— messages after the cursor you have not seen. Counts only rows the transcript renders, and never your own messages. - first_unread_message_id
string | null— the message to draw the divider above, and to pass as thearoundanchor when jumping there. Computed server-side, so a deleted or invisible row right after the cursor cannot become a dead anchor.nullexactly whenunread_countis0. - unread_count_capped
boolean—truewhen more messages are unread thanunread_countreports (the count stops at 999 so the scan stays bounded). Render999+rather than an exact number when set.
# Where did I stop reading?
curl -X GET "https://api.aitronos.com/v1/shared-threads/abc123slug/read-state" \
-H "Authorization: Bearer YOUR_TOKEN"
# I have now read up to this message.
curl -X PUT "https://api.aitronos.com/v1/shared-threads/abc123slug/read-state" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"last_read_message_id": "msg_abc123"}'from aitronos import Aitronos
client = Aitronos(api_key="", token="YOUR_TOKEN")
state = client.threads.shared.get_read_state(slug="abc123slug")
if state.unread_count:
print(f"{state.unread_count} new since {state.last_read_at}")
print(f"divider goes above {state.first_unread_message_id}")
client.threads.shared.set_read_state(
slug="abc123slug",
last_read_message_id="msg_abc123",
)const state = await (
await fetch(
"https://api.aitronos.com/v1/shared-threads/abc123slug/read-state",
{ headers: { Authorization: "Bearer YOUR_TOKEN" } },
)
).json();
await fetch(
"https://api.aitronos.com/v1/shared-threads/abc123slug/read-state",
{
method: "PUT",
headers: {
Authorization: "Bearer YOUR_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ last_read_message_id: "msg_abc123" }),
},
);{
"last_read_message_id": "msg_abc123",
"last_read_at": "2026-08-05T20:07:04Z",
"unread_count": 12,
"first_unread_message_id": "msg_def456",
"unread_count_capped": false
}Open at the bottom with no divider. This is a normal state, not an error.
{
"last_read_message_id": null,
"last_read_at": null,
"unread_count": 0,
"first_unread_message_id": null,
"unread_count_capped": false
}{
"success": false,
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "The requested resource could not be found.",
"system_message": "That message is not part of this thread",
"type": "client_error",
"status": 404,
"details": { "last_read_message_id": "msg_ghost" },
"trace_id": "...",
"timestamp": "..."
}
}The same uniform deny as opening the link — it never reveals whether the link or thread exists.
{
"success": false,
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "The requested resource could not be found.",
"system_message": "Share link not found",
"type": "client_error",
"status": 404,
"details": {},
"trace_id": "...",
"timestamp": "..."
}
}- Get Shared Thread Messages — Read the transcript the marker points into
- Mark Shared Thread Read — Clear the whole thread at once, without naming a message
- Get Shared Thread — Open the share link
- Join Shared Thread — Join a live-shared thread