---
title: "List inbox threads"
method: GET
path: "/inbox/threads/"
tags: ["inbox"]
---

# List inbox threads

`GET /inbox/threads/`

## Request

- Base URL: `https://api.getoneprofile.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.getoneprofile.ai/inbox/threads/`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Newest-first by default, unless `search` is set. Then it is ranked by relevance, most useful match first, and `sort` is ignored. `sort=longest_waiting` reads the same list from the other end, oldest activity first, which is how a reply that has been sitting unanswered for three days gets to the top. Auto-replies and bounces live in their own views; `unlabelled` is the work queue: real threads nobody and no classifier has described yet. `search` runs full-text over every message plus the contact's name and address. `channel` and the `last_message_at__after` / `last_message_at__before` bounds narrow the list; both bounds are inclusive. The working views (`all`, `unlabelled`, `needs_approval`) list only conversations the contact wrote into: a send nobody answered is not in the inbox. `all` and `unlabelled` list only those still waiting on us: a reply with no answer yet, an unread conversation, a reminder that went off, or an answer of ours that went three business days unanswered (`came_back`, `returns_at`) when the verdict on their latest reply does not say the conversation is over. Once we answer, a conversation moves out and stays in `sent`; one marked done (`is_done`) stays out until a new message reaches us. A `search` or a `label_id` reads every conversation the contact wrote into, waiting or not. Each row's `listed_at` is the moment it sorts by. `out_of_office`, `bounces` and `reminders` list what a machine wrote or the person snoozed. `sent` lists every conversation something of ours went out in, a sequence step or a reply, on email or LinkedIn, answered or not and whatever became of it since; a LinkedIn reply still queued for the browser, or one that failed, has not gone out. A send nobody answered never lights the unread badge. Each row's `contact_replied` says whether the contact has written back. `reply_state=replied` keeps the conversations we answered after the contact's latest reply, by a person or the agent; `reply_state=not_replied` keeps the ones whose latest reply is still the last word. `all_mail` lists every conversation, whatever became of it: the place a conversation marked done is found.

## Query parameters

- `view` string
- `unread_only` boolean
- `reply_state` 'replied' | 'not_replied': Whether the contact's latest reply has had an answer from us. A filter over the working inbox, where every conversation is one the contact answered. REPLIED: an answer of ours went out after their latest reply, written by a person or by the agent. A LinkedIn reply queued for the browser counts once nothing waits on a person's approval for it (a person sent it, or its task was approved); one still waiting on Tasks, a failed one and a forward to somebody else do not. NOT_REPLIED: their latest reply is still the last word. Only a genuine reply asks for an answer, so an auto-reply or a bounce arriving after our answer leaves a conversation REPLIED, and a thread whose only inbound mail is automatic is no conversation at all: the inbox does not list it, so neither state does.
- `sequence_id` string, uuid, nullable
- `mailbox_id` string, uuid, nullable
- `sender_profile_id` string, uuid, nullable
- `sequence_id__not` string[], nullable
- `mailbox_id__not` string[], nullable
- `sender_profile_id__not` string[], nullable
- `label_id` string, uuid, nullable
- `channel` 'email' | 'linkedin': Which surface a conversation happens on. Threads are contact-scoped, not channel-scoped, so this says how a thread is REACHED rather than partitioning the inbox: a reply arrives, is classified and stops the cadence identically either way, and only the send path differs.
- `last_message_at__after` string, date-time, nullable
- `last_message_at__before` string, date-time, nullable
- `sort` 'newest' | 'longest_waiting' | 'positives_first' | 'sequence': The order a working inbox is read in. NEWEST is the default and what every mail client does. LONGEST_WAITING is the one a sales team actually triages by: a reply that came in on Monday and still has no answer on Thursday is the most expensive thing in the list, and newest-first buries it under everything that has arrived since. Smartlead builds its whole SLA view on this ordering, and we had no way to reach it: the list was newest-first with no alternative. Deliberately two named READINGS rather than a generic field+direction pair. "Longest waiting" is not `last_message_at asc`: an expired snooze lifts a thread to its wake-up time, so both orderings sort the same computed instant, and exposing the raw column would let a caller ask for an order that disagrees with the one the list actually uses.
- `search` string, nullable
- `limit` integer
- `offset` integer

## Response `200`

Successful Response

- ThreadListReadSchema
  - `threads` ThreadReadSchema[], required
    - `came_back` boolean, required: Back in the Inbox because our answer went three business days unanswered: `returns_at` has passed.
    - `can_reply_on_linkedin` boolean: Whether an explicit channel='linkedin' reply can go out on this thread, because it carries a LinkedIn member identity and a paired account. The composer's channel toggle keys off this.
    - `channel` 'email' | 'linkedin', required: Which surface a conversation happens on. Threads are contact-scoped, not channel-scoped, so this says how a thread is REACHED rather than partitioning the inbox: a reply arrives, is classified and stops the cadence identically either way, and only the send path differs.
    - `contact_email` string, nullable, required
    - `contact_key` string, required
    - `contact_linkedin_url` string, nullable: The contact's LinkedIn profile URL, when our own evidence (invitation ledger, dispatched actions, or the sequence's mapped column) names one. Only the SINGLE-thread read resolves it; the list leaves it null.
    - `contact_name` string, required
    - `contact_replied` boolean, nullable: Whether the contact has written back in this thread: a person's reply, never an auto-reply, a bounce or a robot's acknowledgement. False on a send nobody answered, which the working views never list. Only the thread LIST populates it; single-thread reads leave it null.
    - `created_at` string, date-time, required
    - `done_at` string, date-time, nullable: When a person marked the conversation done. `is_done` says whether the mark still holds.
    - `enrolment_id` string, uuid, nullable: The contact's enrolment in this thread's sequence, when it has one: the id the 'Resume cadence' action targets.
    - `follow_up_expected` boolean, required: Whether an answer of ours they leave unanswered would come back to the Inbox: the contact has written, and the verdict on their latest reply, by the classifier or a label a person set, does not say the conversation is over.
    - `has_pending_draft` boolean: An AI reply is waiting for approval on this thread: the list row's 'Action required' mark. Only the thread LIST populates it; single-thread reads leave it False and the open conversation fetches the draft itself.
    - `id` string, uuid, required
    - `is_bounce` boolean, required
    - `is_done` boolean, required: Marked done, and no new message has reached us since: out of the Inbox. Their reply, or an answer of ours, arriving after the mark ends it, whatever time the message gives itself; an auto-reply, a bounce, a forward or a reply still waiting on Tasks does not.
    - `is_out_of_office` boolean, required
    - `is_unread` boolean, required
    - `label_ids` string[], required
    - `last_message_at` string, date-time, required
    - `last_snippet` string, required
    - `linkedin_account_status` 'pending' | 'active' | 'revoked': Pairing lifecycle. ``PENDING`` exists between "the user asked to connect" and "an extension actually bound a LinkedIn member to it", so the sender page can show a half-finished connection rather than nothing at all.
    - `linkedin_checkpoint_status` 'ok' | 'checkpoint': Whether LinkedIn is currently challenging this account.
    - `linkedin_last_seen_at` string, date-time, nullable: When a browser on that account last checked in; null when none ever has. Single-thread read only; the list leaves it null.
    - `linkedin_presence` 'active' | 'idle' | 'not_seen': How recently ANY browser connected to this sender checked in. Three states, not two, because two different questions are asked of a connection. ``ACTIVE`` answers "can a step run right now": a browser is open and heartbeating. ``IDLE`` answers "is this still set up": the laptop was closed for the evening. ``NOT_SEEN`` is the one that needs a person: nothing has checked in for a day, and queued work is silently waiting.
    - `linkedin_queue_expiry_days` integer, nullable: Always null. The wait window was retired: a queued LinkedIn reply now waits for a person, or for a browser, without expiring. The field stays so older clients still find it; a null hides the composer's sentence about it.
    - `linkedin_sender_profile_id` string, uuid, nullable: The sender the LinkedIn account belongs to, so the composer can send a person to that sender's LinkedIn tab to reconnect. Single-thread read only; the list leaves it null.
    - `listed_at` string, date-time, required: The moment the list sorts this conversation by: its last message, or later when a reminder went off or, on the Inbox's own views, when it came back from silence. The time to show beside the row.
    - `mailbox_id` string, uuid, nullable, required
    - `match_excerpt` string, nullable: The matched message, headlined around the match. Only set when searching.
    - `message_count` integer, required
    - `returns_at` string, date-time, nullable: When this conversation comes back to the Inbox if the contact stays quiet: three business days after our latest answer, at the same time of day, counted in the sender's timezone. In the past once it has come back (`came_back`). Null when it will not: they have the last word, the verdict on their latest reply, by the classifier or a label a person set, says the conversation is over (Negative, Meeting booked, Meeting completed, Closed or Wrong contact), or it is done.
    - `sequence_id` string, uuid, nullable, required
    - `snoozed_until` string, date-time, nullable: Hidden from the working views until this instant.
    - `step_id` string, uuid, nullable, required
    - `stop_reason` 'reply' | 'unsubscribe' | 'bounce' | 'manual' | 'company_reply' | 'row_left_audience' | 'row_left_paused' | 'step_removed' | 'removed' | 'cooldown' | 'rejected' | 'mailbox_removed' | 'trashed' | 'trashed_paused'
    - `subject` string, required
    - `updated_at` string, date-time, required
  - `total` integer, required

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `422`: Invalid request made to the API.
- `500`: Internal server error.
- `502`: Bad gateway error.
- `503`: Service unavailable.

---

[API](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile.md) · [All operations](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile/llms.txt) · [OpenAPI document](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile/revisions/4a6bd71e3f02?raw)
