> ## 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.

# Core concepts

> Understanding the Inbox API data model

## Overview

The Inbox API manages DM conversations across messaging platforms. Understanding these core entities will help you build effective integrations.

<Info>
  **Multi-Platform Future**: The API currently supports X (Twitter) DMs.
  Instagram and LinkedIn are coming in Q2 2026. The data model is
  platform-agnostic — see [Supported platforms](/resources/supported-platforms).
</Info>

## Key entities

### Threads

A **thread** represents a DM conversation between one of your account links and a prospect.

```json theme={null}
{
  "id": "l44e15irdq4db30i77cgphhx",
  "platform": "twitter",
  "platformId": "1566123362161725440:1876543210987654321",
  "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",
  "variant": "xChat",
  "accountLinkId": "df6jbw4h36qm5d9iu2sgn7kx",
  "isSyncing": false,
  "isRequest": false,
  "typingIndicatorsEnabled": null,
  "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": { "..." }
}
```

Key fields:

| Field           | Description                                                                  |
| --------------- | ---------------------------------------------------------------------------- |
| `id`            | Inbox thread ID — use this for all API operations                            |
| `platform`      | The platform this thread belongs to (currently `"twitter"`)                  |
| `platformId`    | The X conversation ID (formatted as `smallerUserId:largerUserId`)            |
| `done`          | Whether the thread is archived                                               |
| `assigneeId`    | Team member assigned to this thread (null if unassigned)                     |
| `status`        | `"active"` (visible in inbox) or `"idle"` (hidden, e.g. Quick Peek previews) |
| `variant`       | `"unencrypted"` (legacy DMs) or `"xChat"` (encrypted DMs)                    |
| `accountLinkId` | Which connected X account this thread belongs to                             |
| `isRequest`     | Whether this is a message request from an unknown prospect                   |
| `lastMessage`   | Preview of the most recent message                                           |
| `prospect`      | The full prospect object (with context) embedded in the thread               |

### Messages

**Messages** are individual DMs sent or received in a thread.

```json theme={null}
{
  "id": "p8rvk2m5j0xn4wq7ybftcael",
  "platform": "twitter",
  "platformId": "1876543210987654322",
  "threadId": "l44e15irdq4db30i77cgphhx",
  "teamId": "hzcai5t59nn9vsck3rbuepyg",
  "authorId": "df6jbw4h36qm5d9iu2sgn7kx",
  "userId": "r3km7xj9wq5p2bvnhfdteoly",
  "campaignId": null,
  "content": "Thanks for reaching out! I'd love to learn more about your project.",
  "origin": "api",
  "createdAt": "2025-01-15T10:30:00.000Z",
  "updatedAt": null,
  "isEdited": false,
  "entities": null,
  "attachment": null,
  "reactions": [],
  "replyData": null,
  "forwardData": null
}
```

Key fields:

| Field        | Description                                                                                                                                                                                                                                                                             |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `content`    | The message text                                                                                                                                                                                                                                                                        |
| `authorId`   | Either the `accountLinkId` or the prospect's `externalId` — identifies the sender within Inbox (not a platform ID)                                                                                                                                                                      |
| `userId`     | Inbox user ID if sent from Inbox by a team member. `null` if the message is from the prospect, was sent from the X client, or was otherwise sent outside of Inbox. A `null` value does not necessarily mean the message is from the prospect — it just means it wasn't sent from Inbox. |
| `origin`     | Where the message originated: `"external"` (pulled from X), `"internal"` (sent from the Inbox UI or a campaign), `"api"` (sent via the API), or `null`                                                                                                                                  |
| `campaignId` | Non-null if the message was sent by a campaign (these have `origin: "internal"`)                                                                                                                                                                                                        |
| `isEdited`   | Whether the message has been edited                                                                                                                                                                                                                                                     |

<Note>
  There is no `direction` field on messages. Check `authorId` against your account link IDs to determine the sender. See [Working with messages — Determining message direction](/guides/messages#determining-message-direction) for details.
</Note>

### Prospects

A **prospect** represents an external user on a messaging platform.

```json theme={null}
{
  "platform": "twitter",
  "platformId": "1876543210987654321",
  "externalId": "hzcai5t59nn9vsck3rbuepyg",
  "documentId": 858224163,
  "displayName": "Jordan Rivera",
  "username": "jordanrivera",
  "handle": "@jordanrivera",
  "image": "https://pbs.twimg.com/profile_images/.../photo.jpg",
  "imageNormalized": "https://pbs.twimg.com/profile_images/.../photo.jpg",
  "bio": "Head of Growth at TechCo. Building the future of SaaS.",
  "location": "San Francisco, CA",
  "profileUrl": "https://x.com/jordanrivera",
  "websiteUrl": "https://techco.com",
  "websiteDomain": "techco.com",
  "verified": "none",
  "profileType": "personal",
  "isProtected": false,
  "followerCount": 12400,
  "followingCount": 892,
  "postCount": 3420,
  "engagementCount": 8100,
  "listedCount": 18,
  "platformCreatedAt": "2019-03-15T08:00:00.000Z",
  "firstSeenAt": "2024-07-09T14:08:49.000Z",
  "lastUpdatedAt": "2025-01-15T10:30:00.000Z",
  "lastEnrichedAt": "2025-01-15T10:30:00.000Z",
  "lastActiveAt": "2025-01-14T12:00:00.000Z",
  "source": "cached",
  "isFresh": true,
  "isStale": false,
  "confidence": 0.85,
  "platformData": {
    "isVerifiedBlue": false,
    "isVerifiedGold": false,
    "isVerifiedGray": false,
    "professionalCategory": null,
    "urlEntities": [],
    "bannerUrl": "https://pbs.twimg.com/profile_banners/...",
    "tweetsCount": 3420,
    "favoritesCount": 8100,
    "rawData": null
  }
}
```

Key differences from what you might expect:

| Field        | Not this          | It's this                                                                          |
| ------------ | ----------------- | ---------------------------------------------------------------------------------- |
| Display name | `name`            | `displayName`                                                                      |
| Bio          | `description`     | `bio`                                                                              |
| Avatar       | `profileImageUrl` | `image` (or `imageNormalized` for highest quality)                                 |
| Followers    | `followersCount`  | `followerCount` (singular)                                                         |
| Verified     | `true` / `false`  | `"verified"` (blue check), `"business"` (gold), `"government"` (gray), or `"none"` |
| Profile type | —                 | `"personal"`, `"business"`, or `"government"`                                      |
| Source       | —                 | `"cached"`, `"indexed"`, `"live"`, or `"merged"`                                   |

Prospects are shared across all account links in your team. When retrieved as part of a thread or via the prospects endpoints, they include a `context` object with your team's custom data.

### Prospect context

When you retrieve a prospect (via threads or the prospects API), the response includes a `context` object:

```json theme={null}
{
  "context": {
    "tags": ["t8rvk2m5j0xn4wq7ybftcael"],
    "statusId": "s3km7xj9wq5p2bvnhfdteoly",
    "valuation": 50000,
    "notes": "Decision maker at TechCo. Follow up next week."
  }
}
```

| Field       | Type             | Description                               |
| ----------- | ---------------- | ----------------------------------------- |
| `tags`      | `string[]`       | Array of tag IDs applied to this prospect |
| `statusId`  | `string \| null` | Pipeline status ID                        |
| `valuation` | `number \| null` | Deal value                                |
| `notes`     | `string \| null` | Free-text notes                           |

Update context with `PATCH /prospects/{prospectId}/context`:

```typescript theme={null}
await client.patch(`/prospects/${prospectId}/context`, {
  notes: "Interested in Enterprise plan",
  valuation: 50000,
  statusId: "s3km7xj9wq5p2bvnhfdteoly",
  addTags: ["t8rvk2m5j0xn4wq7ybftcael"],
  removeTags: ["old_tag_id_here"],
});
```

<Note>
  Tags are modified with `addTags` and `removeTags` — there is no `tagIds` field. See [Tags & statuses](/guides/tags-and-statuses) for details.
</Note>

### Account links

An **account link** is an X account connected to your team.

```json theme={null}
{
  "id": "df6jbw4h36qm5d9iu2sgn7kx",
  "platform": "twitter",
  "platformId": "1566123362161725440",
  "name": "Acme Corp",
  "username": "acmecorp",
  "image": "https://pbs.twimg.com/profile_images/.../photo.jpg",
  "createdAt": "2024-06-01T12:00:00.000Z",
  "status": "active",
  "syncedAt": "2025-01-15T10:30:00.000Z"
}
```

Use account links to:

* Send messages from specific accounts
* Filter threads by account
* Manage multi-account workflows

### Tags

**Tags** are flexible labels you apply to prospects for organization.

```json theme={null}
{
  "id": "t8rvk2m5j0xn4wq7ybftcael",
  "name": "Hot Lead",
  "color": "#ef4444"
}
```

A prospect can have multiple tags. Colors accept either a **predefined name** (e.g., `"red"`, `"blue"`, `"green"`) or a **hex code** (e.g., `"#ef4444"`). Use `GET /colors` to see all predefined options.

### Statuses

**Statuses** represent pipeline stages for a prospect.

```json theme={null}
{
  "id": "s3km7xj9wq5p2bvnhfdteoly",
  "name": "Qualified",
  "color": "#f59e0b",
  "createdAt": "2024-06-01T12:00:00.000Z"
}
```

A prospect can have only one status at a time. Use statuses for linear progression (e.g., New → Qualified → Won). Use tags for non-linear attributes.

### Members

**Members** are users on your team.

```json theme={null}
{
  "id": "r3km7xj9wq5p2bvnhfdteoly",
  "userId": "usr4hn8xj2wq5p1bvnhfdteo",
  "name": "Alex Chen",
  "email": "alex@acmecorp.com",
  "image": "https://lh3.googleusercontent.com/...",
  "role": "admin",
  "createdAt": "2024-06-01T12:00:00.000Z",
  "updatedAt": null
}
```

Roles are `"owner"`, `"admin"`, or `"user"`. Member IDs are used for assigning threads and filtering by assignee.

## The dual ID system

Inbox uses two types of IDs for platform-related objects:

<CardGroup cols={2}>
  <Card title="Inbox ID (id)" icon="database">
    e.g. `l44e15irdq4db30i77cgphhx`

    The internal Inbox ID (CUID2 format). Use this for all API operations.
  </Card>

  <Card title="Platform ID (platformId)" icon="hash">
    e.g. `1876543210987654321`

    The original ID from X. Use this for lookups and constructing X URLs.
  </Card>
</CardGroup>

```typescript theme={null}
// Use Inbox IDs for API operations
await client.get(`/threads/${thread.id}/messages`);

// Use Platform IDs for lookups
await client.get("/threads/lookup", {
  params: {
    externalPlatformId: "1876543210987654321",
    accountLinkId: account.id,
  },
});
```

### Helpful X URLs

You can use platform IDs to construct direct links to X:

| Link type                  | URL pattern                                     |
| -------------------------- | ----------------------------------------------- |
| User profile (by ID)       | `https://x.com/i/user/{platformId}`             |
| User profile (by username) | `https://x.com/{username}`                      |
| Tweet/Post                 | `https://x.com/anyusername/status/{tweetId}`    |
| DM conversation            | `https://x.com/i/chat/{conversationPlatformId}` |

<Note>
  The user-by-ID link (`/i/user/{platformId}`) is the most reliable since
  usernames can change. For tweet URLs, X ignores the username — only the tweet
  ID matters.
</Note>

## Thread status vs Inbox views

The thread `status` field (`"active"` or `"idle"`) controls whether the thread is visible in the inbox. Active threads appear in inbox views; idle threads are hidden (e.g., threads created via Quick Peek that haven't had a real conversation yet).

This is separate from **inbox views**, which are filtered using the `inbox` query parameter:

| `inbox` value | Description                                                   |
| ------------- | ------------------------------------------------------------- |
| `"default"`   | Active conversations needing attention                        |
| `"no-reply"`  | The prospect sent the last message and is awaiting your reply |
| `"requests"`  | New message requests from unknown prospects                   |
| `"archived"`  | Threads marked as done (`done: true`)                         |

Filter by inbox view:

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

// Threads where the prospect is awaiting your reply
const { data: noReply } = await client.get("/threads", {
  params: { inbox: "no-reply" },
});

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

## Response shapes

Different endpoints return data in different shapes. Here's what to expect:

| Pattern       | Endpoints                                                          | Shape                                                           |
| ------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
| Single object | `GET /threads/{id}`, `POST /threads`, `GET /threads/lookup`        | Returns the object directly                                     |
| Array         | `GET /tags`, `GET /statuses`, `GET /account-links`, `GET /members` | Returns an array directly                                       |
| Paginated     | `GET /threads`, `GET /threads/{id}/messages`                       | Returns `{ threads, nextCursor }` or `{ messages, nextCursor }` |

```typescript theme={null}
// Direct object — no wrapper
const { data: thread } = await client.get(`/threads/${threadId}`);
console.log(thread.id);

// Direct array — no wrapper
const { data: tags } = await client.get("/tags");
console.log(tags.length);

// Paginated — has wrapper
const { data } = await client.get("/threads");
console.log(data.threads.length, data.nextCursor);
```

## Pagination

List endpoints use cursor-based pagination:

```typescript theme={null}
const allThreads = [];
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);
```

<Tip>
  Use the flat `cursorId` and `cursorTimestamp` parameters instead of bracket
  notation (`cursor[id]`). The bracket notation form is deprecated.
</Tip>

## Entity relationships

```
Team
├── Members (team users)
├── Account Links (connected X accounts)
├── Tags (labels for prospects)
├── Statuses (pipeline stages)
└── Prospects (external X users)
    ├── Context (tags, status, notes, valuation)
    └── Threads (one per account link + prospect pair)
        └── Messages
```

* A prospect can have **multiple tags** but only **one status**
* A thread belongs to exactly **one account link** and **one prospect**
* A prospect can have threads across **multiple account links**

## Next steps

<CardGroup cols={2}>
  <Card title="Managing threads" icon="messages-square" href="/guides/managing-threads">
    Thread operations and filtering
  </Card>

  <Card title="Working with messages" icon="message-circle" href="/guides/messages">
    Send and receive messages
  </Card>

  <Card title="Working with prospects" icon="user" href="/guides/prospects">
    Manage prospect data and context
  </Card>

  <Card title="Tags & statuses" icon="tags" href="/guides/tags-and-statuses">
    Organize with tags and pipeline stages
  </Card>
</CardGroup>
