> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inboxapp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Managing threads

> Working with conversations in the Inbox API

## Listing threads

Retrieve threads with filtering and pagination:

<CodeGroup>
  ```typescript list.ts theme={null}
  const { data } = await client.get("/threads", {
    params: {
      limit: 20,
      inbox: "default",
    },
  });

  console.log(`Found ${data.threads.length} threads`);
  data.threads.forEach((thread) => {
    console.log(`${thread.id} — @${thread.prospect.username}`);
  });
  ```

  ```bash cURL theme={null}
  curl "https://inboxapp.com/api/v1/threads?inbox=default&limit=20" \
    -H "Authorization: Bearer $INBOX_API_TOKEN"
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "threads": [
    {
      "id": "l44e15irdq4db30i77cgphhx",
      "platform": "twitter",
      "done": false,
      "assigneeId": null,
      "lastMessageTimestamp": "2025-01-15T15:30:00.000Z",
      "computedSortTimestamp": "2025-01-15T15:30:00.000Z",
      "createdAt": "2025-01-10T10:00:00.000Z",
      "status": "active",
      "accountLinkId": "df6jbw4h36qm5d9iu2sgn7kx",
      "isSyncing": false,
      "isRequest": false,
      "lastMessage": {
        "id": "p8rvk2m5j0xn4wq7ybftcael",
        "content": "Sounds great, let's schedule a call!",
        "authorId": "hzcai5t59nn9vsck3rbuepyg",
        "createdAt": "2025-01-15T15:30:00.000Z",
        "userId": null,
        "campaignId": null
      },
      "prospect": {
        "platform": "twitter",
        "platformId": "1876543210987654321",
        "displayName": "Jordan Rivera",
        "username": "jordanrivera",
        "handle": "@jordanrivera",
        "image": "https://pbs.twimg.com/profile_images/.../photo.jpg",
        "bio": "Head of Growth at TechCo",
        "followerCount": 12400,
        "verified": "none",
        "context": {
          "tags": [],
          "statusId": null,
          "valuation": null,
          "notes": null
        },
        "..."
      }
    }
  ],
  "nextCursor": {
    "id": "l44e15irdq4db30i77cgphhx",
    "timestamp": "2025-01-15T15:30:00.000Z"
  }
}
```

<Note>
  The default page size is 20 (max 100). Use `nextCursor` to paginate.
</Note>

## Looking up threads by username

Find all threads for a prospect by their exact platform username:

<CodeGroup>
  ```typescript lookup-username.ts theme={null}
  const { data: threads } = await client.get("/threads/lookup-by-username", {
    params: {
      username: "jordanrivera",
    },
  });

  threads.forEach((thread) => {
    console.log(`${thread.id} — ${thread.accountLinkId}`);
  });
  ```

  ```bash cURL theme={null}
  curl "https://inboxapp.com/api/v1/threads/lookup-by-username?username=jordanrivera" \
    -H "Authorization: Bearer $INBOX_API_TOKEN"
  ```
</CodeGroup>

Returns a flat array of `Thread` objects — one per account link that has a conversation with that prospect. No pagination; all matching threads are returned.

### Filter by account link

Scope results to specific connected accounts:

```typescript theme={null}
const { data: threads } = await client.get("/threads/lookup-by-username", {
  params: {
    username: "jordanrivera",
    "accountLinkIds[0]": "df6jbw4h36qm5d9iu2sgn7kx",
  },
});
```

<Note>
  The `username` must be an exact match (case-insensitive, without the `@` prefix). For lookup by platform ID, use [GET /threads/lookup](/api-reference/threads/lookup-thread) instead.
</Note>

## Filtering threads

### By Inbox view

The `inbox` parameter filters by thread state:

```typescript theme={null}
// Active conversations
const active = await client.get("/threads", { params: { inbox: "default" } });

// Prospect sent the last message — you need to reply
const noReply = await client.get("/threads", { params: { inbox: "no-reply" } });

// New message requests
const requests = await client.get("/threads", {
  params: { inbox: "requests" },
});

// Archived (done)
const archived = await client.get("/threads", {
  params: { inbox: "archived" },
});
```

### By account link

Filter threads to specific connected accounts. Pass an array of IDs via `accountLinkIds`:

```typescript theme={null}
const { data } = await client.get("/threads", {
  params: {
    "accountLinkIds[0]": "df6jbw4h36qm5d9iu2sgn7kx",
  },
});
```

### By tags

```typescript theme={null}
// Prospects with ANY of these tags
const { data } = await client.get("/threads", {
  params: {
    "filters[tags][selectedIds][0]": "t8rvk2m5j0xn4wq7ybftcael",
    "filters[tags][selectedIds][1]": "u9swl3n6k1yo5xr8zcgudbfm",
  },
});

// Prospects with NO tags
const { data: untagged } = await client.get("/threads", {
  params: {
    "filters[tags][noTags]": true,
  },
});
```

### By status

```typescript theme={null}
// Threads with prospects in specific statuses
const { data } = await client.get("/threads", {
  params: {
    "filters[statuses][selectedIds][0]": "s3km7xj9wq5p2bvnhfdteoly",
  },
});

// Prospects with no status
const { data: noStatus } = await client.get("/threads", {
  params: {
    "filters[statuses][noStatus]": true,
  },
});
```

### By assignee

```typescript theme={null}
// Assigned to specific member
const { data } = await client.get("/threads", {
  params: {
    "filters[assignees][selectedIds][0]": "r3km7xj9wq5p2bvnhfdteoly",
  },
});

// Unassigned threads
const { data: unassigned } = await client.get("/threads", {
  params: {
    "filters[assignees][noAssignee]": true,
  },
});
```

### By campaign

```typescript theme={null}
// Threads from specific campaigns
const { data } = await client.get("/threads", {
  params: {
    "filters[campaigns][selectedIds][0]": "c5nwp8r2j3ym6at0xbkqhfvg",
  },
});

// Threads not from any campaign
const { data: noCampaign } = await client.get("/threads", {
  params: {
    "filters[campaigns][noCampaign]": true,
  },
});
```

### Sort order

```typescript theme={null}
const { data } = await client.get("/threads", {
  params: {
    "filters[sortOrder]": "desc", // or 'asc'
  },
});
```

### Combined filters

Filters combine with AND logic:

```typescript theme={null}
// Active threads, qualified status, assigned to a specific member
const { data } = await client.get("/threads", {
  params: {
    inbox: "default",
    "accountLinkIds[0]": "df6jbw4h36qm5d9iu2sgn7kx",
    "filters[statuses][selectedIds][0]": "s3km7xj9wq5p2bvnhfdteoly",
    "filters[assignees][selectedIds][0]": "r3km7xj9wq5p2bvnhfdteoly",
    limit: 20,
  },
});
```

### Filter helper

A utility to build filter params:

```typescript theme={null}
function buildFilters(opts: {
  tagIds?: string[];
  statusIds?: string[];
  assigneeIds?: string[];
  campaignIds?: string[];
  noTags?: boolean;
  noStatus?: boolean;
  noAssignee?: boolean;
  noCampaign?: boolean;
  sortOrder?: "asc" | "desc";
}) {
  const params: Record<string, string | boolean> = {};

  opts.tagIds?.forEach((id, i) => {
    params[`filters[tags][selectedIds][${i}]`] = id;
  });
  opts.statusIds?.forEach((id, i) => {
    params[`filters[statuses][selectedIds][${i}]`] = id;
  });
  opts.assigneeIds?.forEach((id, i) => {
    params[`filters[assignees][selectedIds][${i}]`] = id;
  });
  opts.campaignIds?.forEach((id, i) => {
    params[`filters[campaigns][selectedIds][${i}]`] = id;
  });

  if (opts.noTags) params["filters[tags][noTags]"] = true;
  if (opts.noStatus) params["filters[statuses][noStatus]"] = true;
  if (opts.noAssignee) params["filters[assignees][noAssignee]"] = true;
  if (opts.noCampaign) params["filters[campaigns][noCampaign]"] = true;
  if (opts.sortOrder) params["filters[sortOrder]"] = opts.sortOrder;

  return params;
}

const { data } = await client.get("/threads", {
  params: {
    inbox: "default",
    ...buildFilters({ tagIds: ["t8rvk2m5j0xn4wq7ybftcael"], noAssignee: true }),
    limit: 50,
  },
});
```

## Pagination

Use `cursorId` and `cursorTimestamp` to paginate through all threads:

```typescript theme={null}
async function getAllThreads() {
  const allThreads: any[] = [];
  let cursorId: string | undefined;
  let cursorTimestamp: string | undefined;

  do {
    const { data } = await client.get("/threads", {
      params: {
        limit: 100,
        ...(cursorId && { cursorId, cursorTimestamp }),
      },
    });

    allThreads.push(...data.threads);
    cursorId = data.nextCursor?.id;
    cursorTimestamp = data.nextCursor?.timestamp;
  } while (cursorId);

  return allThreads;
}
```

See [Pagination](/reference/pagination) for streaming patterns and error handling.

## Looking up a thread

Find an existing thread between an account and a prospect:

<CodeGroup>
  ```typescript lookup.ts theme={null}
  const { data: thread } = await client.get("/threads/lookup", {
    params: {
      externalPlatformId: "1876543210987654321",
      accountLinkId: "df6jbw4h36qm5d9iu2sgn7kx",
    },
  });

  if (thread) {
    console.log("Found thread:", thread.id);
  } else {
    console.log("No thread exists yet");
  }
  ```

  ```bash cURL theme={null}
  curl "https://inboxapp.com/api/v1/threads/lookup?externalPlatformId=1876543210987654321&accountLinkId=df6jbw4h36qm5d9iu2sgn7kx" \
    -H "Authorization: Bearer $INBOX_API_TOKEN"
  ```
</CodeGroup>

The lookup endpoint accepts either `externalPlatformId` (X user ID) or `externalId` (Inbox external ID). At least one must be provided along with `accountLinkId`.

Returns the `Thread` object directly, or `null` if no thread exists.

## Creating a thread

Create a new conversation thread:

```typescript theme={null}
const { data: thread } = await client.post("/threads", {
  externalPlatformId: "1876543210987654321",
  accountLinkId: "df6jbw4h36qm5d9iu2sgn7kx",
});

console.log("Created thread:", thread.id);
```

<Note>
  Creating a thread doesn't send a message. Use the [messages
  endpoint](/api-reference/messages/send-message) to send a DM.
</Note>

<Warning>
  If a thread already exists between the account link and prospect, the API will
  return an error. Use [lookup](/api-reference/threads/lookup-thread) first to
  check, or use the [lookup-or-create pattern](#lookup-or-create-pattern) below.
</Warning>

## Getting thread details

```typescript theme={null}
const { data: thread } = await client.get(`/threads/${threadId}`);

console.log("Thread:", thread.id);
console.log("Prospect:", thread.prospect.displayName);
console.log("Last message:", thread.lastMessage?.content);
```

Returns the `Thread` object directly — not wrapped in `{ thread: ... }`.

## Updating threads

Modify thread assignment and done status:

```typescript theme={null}
// Assign thread to a team member
await client.patch(`/threads/${threadId}`, {
  assigneeId: "r3km7xj9wq5p2bvnhfdteoly",
});

// Mark thread as done (archives it)
await client.patch(`/threads/${threadId}`, {
  done: true,
});

// Unassign and reopen
await client.patch(`/threads/${threadId}`, {
  assigneeId: null,
  done: false,
});
```

## Deleting threads

Soft-delete a thread:

```typescript theme={null}
await client.delete(`/threads/${threadId}`);
```

<Warning>
  Deletion is permanent and cannot be undone. The thread is also deleted on the
  X platform. Consider using `done: true` to archive instead.
</Warning>

## Common workflows

### Lookup-or-create pattern

```typescript theme={null}
async function getOrCreateThread(
  prospectPlatformId: string,
  accountLinkId: string,
) {
  const { data: existing } = await client.get("/threads/lookup", {
    params: { externalPlatformId: prospectPlatformId, accountLinkId },
  });

  if (existing) return existing;

  const { data: created } = await client.post("/threads", {
    externalPlatformId: prospectPlatformId,
    accountLinkId,
  });

  return created;
}
```

### Round-robin assignment

```typescript theme={null}
const memberIds = ["r3km7xj9wq5p2bvnhfdteoly", "s4ln8yk0xr6q3cw1behsifup", "t5mo9zl1ys7r4dx2cfitjgvqw"];
let idx = 0;

async function assignRoundRobin(threadId: string) {
  await client.patch(`/threads/${threadId}`, {
    assigneeId: memberIds[idx],
  });
  idx = (idx + 1) % memberIds.length;
}
```

### Get threads needing follow-up

```typescript theme={null}
const { data } = await client.get("/threads", {
  params: {
    inbox: "no-reply",
    limit: 50,
  },
});

console.log(`${data.threads.length} threads awaiting your reply`);
```

## Related endpoints

<CardGroup cols={2}>
  <Card title="List threads" icon="list-tree" href="/api-reference/threads/list-threads">
    GET /threads
  </Card>

  <Card title="Lookup by username" icon="at-sign" href="/api-reference/threads/lookup-by-username">
    GET /threads/lookup-by-username
  </Card>

  <Card title="Create thread" icon="message-square-plus" href="/api-reference/threads/create-thread">
    POST /threads
  </Card>

  <Card title="Lookup thread" icon="text-search" href="/api-reference/threads/lookup-thread">
    GET /threads/lookup
  </Card>

  <Card title="Update thread" icon="pencil" href="/api-reference/threads/update-thread">
    PATCH /threads/{threadId}
  </Card>

  <Card title="Delete thread" icon="trash" href="/api-reference/threads/delete-thread">
    DELETE /threads/{threadId}
  </Card>

  <Card title="Working with messages" icon="messages-square" href="/guides/messages">
    Send and receive messages in threads
  </Card>
</CardGroup>
