---
title: "Read the member's read state for threads"
method: GET
path: "/threads/read-state"
tags: ["Threads"]
---

# Read the member's read state for threads

`GET /threads/read-state`

## Request

- Base URL: `https://api.primitive.dev/v1`
- URL: `https://api.primitive.dev/v1/threads/read-state`
- Auth: HTTP bearer

The signed-in member's read cursor and `unread_count` per thread, computed together in one query. Two modes, exactly one per request. With `thread_ids`, answers those threads (ids that are not threads of the organization are left out). With `updated_since`, returns the cursors that moved after that instant, oldest change first, paged by `meta.cursor`: poll it from each device to learn what was read elsewhere. The feed lags real time by about 5 seconds: a cursor appears once it moved at least that long ago, so a write still committing is never skipped. When `meta.cursor` is null, poll again later from the newest `updated_at` you have seen, overlapping by a few seconds; repeated rows are harmless. Only a signed-in organization member (a dashboard session or an OAuth member grant) can use this. Organization API keys, agent connection keys and Functions are refused with 403: read state is personal to a member.

## Query parameters

- `thread_ids` string
- `updated_since` string, date-time
- `cursor` string
- `limit` integer

## Response `200`

Success

- object
  - `success` true, required
  - `data` ThreadReadState[], required
    - `thread_id` string, uuid, required
    - `last_read_at` string, date-time, nullable, required: Arrival time of the newest message the member has read in this thread, in microsecond UTC form, or null when the member never marked it. Sending it back as `up_to` names the same instant; a timestamp cursor covers every message that arrived at that instant, so it is at or ahead of an id cursor at the same instant.
    - `last_read_email_id` string, uuid, nullable, required: The email or sent email the cursor was set from, or null (never marked, or marked by `up_to`).
    - `unread_count` integer, required: Inbound mail in the thread that arrived after the cursor, counting only mail from others: informational signals (`fyi: true`, such as read and acknowledgement notices) and mail the member sent from their own address are not counted. A thread the member never marked counts all of it.
    - `updated_at` string, date-time, nullable, required: When the cursor last moved, or null when it never did. Orders the `updated_since` change feed.
  - `meta` object
    - `limit` integer
    - `cursor` string, nullable

## Other responses

- `400`: Invalid request parameters
- `401`: Invalid or missing API key
- `403`: Authenticated caller lacks permission for the operation
- `429`: Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.

## Changes

> 32 revisions in range; 1 not diffed.

- **2026-10-21** `0360ea8d4f3d`: 52 warning
  - added the new `agent_changed` enum value to the `error/code` response property for the response status `400`
  - added the new `agent_changed` enum value to the `error/code` response property for the response status `401`
  - added the new `agent_changed` enum value to the `error/code` response property for the response status `403`
  - added the new `agent_changed` enum value to the `error/code` response property for the response status `429`
  - …48 more
- **2026-10-21** `5279a7bed7ac`: 44 warning
  - added the new `archive_busy` enum value to the `error/code` response property for the response status `400`
  - added the new `archive_busy` enum value to the `error/code` response property for the response status `401`
  - added the new `archive_busy` enum value to the `error/code` response property for the response status `403`
  - added the new `archive_busy` enum value to the `error/code` response property for the response status `429`
  - …40 more
- …earlier changes not shown

[Operation history](https://skmtc.dev/primitive/apis/primitive-api/changes/threads/read-state/get.md)

---

[API](https://skmtc.dev/primitive/apis/primitive-api.md) - [All operations](https://skmtc.dev/primitive/apis/primitive-api/llms.txt) - [OpenAPI document](https://skmtc.dev/primitive/apis/primitive-api/revisions/bce409c97acb?raw)
