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

# Working with prospects

> Managing prospect data and context in the Inbox API

## Overview

Prospects represent external users on messaging platforms. This guide covers retrieving prospect data, updating context, and using prospect information for lead management.

## Prospect data model

The prospect object contains profile data synced from the platform, plus a `context` object with your team's custom data.

```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,
    "bannerUrl": "https://pbs.twimg.com/profile_banners/...",
    "tweetsCount": 3420,
    "favoritesCount": 8100
  },
  "context": {
    "tags": ["t8rvk2m5j0xn4wq7ybftcael"],
    "statusId": "s3km7xj9wq5p2bvnhfdteoly",
    "valuation": 50000,
    "notes": "Decision maker at TechCo. Follow up next week.",
    "threads": [
      {
        "id": "l44e15irdq4db30i77cgphhx",
        "accountLinkId": "df6jbw4h36qm5d9iu2sgn7kx",
        "lastMessageTimestamp": "2025-01-15T15:30:00.000Z",
        "createdAt": "2025-01-10T10:00:00.000Z"
      }
    ]
  }
}
```

### Key fields

| Field             | Description                                                                        |
| ----------------- | ---------------------------------------------------------------------------------- |
| `platform`        | Always `"twitter"` for now                                                         |
| `platformId`      | The X numeric user ID (string)                                                     |
| `externalId`      | Inbox internal ID. `null` if the prospect hasn't been imported to the database yet |
| `displayName`     | Display name shown on their profile                                                |
| `username`        | Handle without `@`                                                                 |
| `handle`          | Handle with `@` prefix (e.g., `@jordanrivera`)                                     |
| `image`           | Profile picture URL from platform                                                  |
| `imageNormalized` | Profile picture URL normalized to highest quality — prefer this for display        |
| `bio`             | Profile bio/description text                                                       |
| `verified`        | `"none"`, `"verified"` (blue check), `"business"` (gold), or `"government"` (gray) |
| `profileType`     | `"personal"`, `"business"`, or `"government"`                                      |
| `followerCount`   | Number of followers (singular, not `followersCount`)                               |
| `confidence`      | Data accuracy score from `0` to `1` — higher is more reliable                      |

### Context fields

The `context` object contains your team's custom data for this prospect:

| 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                                    |
| `threads`   | `array \| null`  | Threads across all account links for this prospect |

<Note>
  Tags, status, valuation, and notes are stored on prospects. Assignee and
  done/not-done technically live on **threads** — see [Assigning and Marking
  Done](#assigning-and-marking-done) for details.
</Note>

## Finding prospects

### Lookup by platform ID

Find a prospect using their X user ID:

<CodeGroup>
  ```typescript lookup.ts theme={null}
  const { data } = await client.get("/prospects/lookup", {
    params: {
      platformId: "1876543210987654321",
    },
  });

  if (data) {
    console.log(`Found: @${data.username}`);
    console.log(`Followers: ${data.followerCount}`);
  } else {
    console.log("Prospect not found");
  }
  ```

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

Returns the prospect with context directly, or `null` if not found.

<Warning>
  Not all prospects are searchable via the API. Lookup is only guaranteed to
  return a prospect if your team has interacted with them (e.g., they messaged
  you or you started a conversation). New users who haven't interacted with
  anyone on the platform may not be found. We're working on improving this.
</Warning>

### Get by Inbox ID

Retrieve a specific prospect by their Inbox ID:

```typescript theme={null}
const { data } = await client.get(`/prospects/${prospectId}`);
console.log(`Prospect: @${data.username}`);
console.log(`Tags: ${data.context.tags.length}`);
```

Returns the prospect with context directly.

<Note>
  Prospects are automatically created when they message you or when you start a
  conversation with them.
</Note>

## Updating prospect context

Update tags, status, valuation, and notes on a prospect. You can also update `assigneeId` and `done` — these are applied to all threads belonging to this prospect on your team.

### Setting notes and valuation

```typescript theme={null}
await client.patch(`/prospects/${prospectId}/context`, {
  notes: "Interested in Enterprise plan. Follow up next week.",
  valuation: 50000,
});
```

### Applying tags

Tags are updated incrementally using `addTags` and `removeTags`:

```typescript theme={null}
// Add tags
await client.patch(`/prospects/${prospectId}/context`, {
  addTags: ["t8rvk2m5j0xn4wq7ybftcael", "u9swl3n6k1yo5xr8zcgudbfm"],
});

// Remove a tag
await client.patch(`/prospects/${prospectId}/context`, {
  removeTags: ["t8rvk2m5j0xn4wq7ybftcael"],
});

// Add and remove in one request
await client.patch(`/prospects/${prospectId}/context`, {
  addTags: ["v0txm4o7l2zp6ys9adhvecgn"],
  removeTags: ["u9swl3n6k1yo5xr8zcgudbfm"],
});
```

<Warning>
  There is no `tagIds` field. Tags are always modified incrementally with
  `addTags` and `removeTags`.
</Warning>

### Setting status

```typescript theme={null}
// Get status IDs first
const { data: statuses } = await client.get("/statuses");
const qualifiedStatus = statuses.find((s) => s.name === "Qualified");

// Set prospect status
await client.patch(`/prospects/${prospectId}/context`, {
  statusId: qualifiedStatus.id,
});

// Clear status
await client.patch(`/prospects/${prospectId}/context`, {
  statusId: null,
});
```

### Assigning and marking done

```typescript theme={null}
// Assign a prospect to a team member
await client.patch(`/prospects/${prospectId}/context`, {
  assigneeId: "r3km7xj9wq5p2bvnhfdteoly",
});

// Mark a prospect as done
await client.patch(`/prospects/${prospectId}/context`, {
  done: true,
});

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

<Note>
  `assigneeId` and `done` technically live on threads, not on the prospect
  itself. When you set these via the prospect update endpoint, the change is
  applied to **all threads** for that prospect on your team. If you have
  multiple account links talking to the same prospect, every thread with that
  prospect will be assigned or marked done. Keep this in mind for multi-account
  setups.
</Note>

### Complete update

Multiple context fields can be updated in a single request:

```typescript theme={null}
await client.patch(`/prospects/${prospectId}/context`, {
  notes: "Decision maker at Acme Corp",
  valuation: 75000,
  addTags: ["t8rvk2m5j0xn4wq7ybftcael"],
  statusId: "s4ln8yk0xr6q3cw1behsifup",
});
```

## Prospect lookup vs thread lookup

Two lookup patterns exist for different use cases:

| Use Case                       | Endpoint                | Returns                           |
| ------------------------------ | ----------------------- | --------------------------------- |
| Get prospect profile + context | `GET /prospects/lookup` | Prospect with context (or `null`) |
| Find conversation thread       | `GET /threads/lookup`   | Thread object (or `null`)         |

```typescript theme={null}
// When you need prospect profile data
const prospect = await client.get("/prospects/lookup", {
  params: { platformId: "1876543210987654321" },
});

// When you need the conversation
const thread = await client.get("/threads/lookup", {
  params: {
    externalPlatformId: "1876543210987654321",
    accountLinkId: "df6jbw4h36qm5d9iu2sgn7kx",
  },
});
```

## Common workflows

### Lead scoring

Calculate a score based on prospect profile:

```typescript theme={null}
function calculateLeadScore(prospect: any): number {
  let score = 0;

  // Follower count scoring
  if (prospect.followerCount > 100000) score += 30;
  else if (prospect.followerCount > 10000) score += 20;
  else if (prospect.followerCount > 1000) score += 10;

  // Verification bonus
  if (prospect.verified !== "none") score += 15;

  // Bio keywords
  const bio = prospect.bio?.toLowerCase() || "";
  const keywords = ["founder", "ceo", "director", "head of", "vp"];
  if (keywords.some((k) => bio.includes(k))) score += 25;

  // Has website
  if (prospect.websiteUrl) score += 10;

  return score;
}

// Usage
const { data: prospect } = await client.get(`/prospects/${prospectId}`);
const score = calculateLeadScore(prospect);
console.log(`Lead score: ${score}/100`);

// Auto-tag based on score
if (score >= 70) {
  await client.patch(`/prospects/${prospect.externalId}/context`, {
    addTags: [highValueTagId],
  });
}
```

### Finding prospects by tag

Prospects aren't directly searchable — search threads instead, which include the prospect:

```typescript theme={null}
// Get all threads with "Enterprise" tag
const { data } = await client.get("/threads", {
  params: {
    inbox: "default",
    "filters[tags][selectedIds][0]": enterpriseTagId,
  },
});

// Extract prospect info from threads
const enterpriseProspects = data.threads.map((thread) => ({
  externalId: thread.prospect.externalId,
  username: thread.prospect.username,
  followerCount: thread.prospect.followerCount,
  threadId: thread.id,
}));

console.log(`${enterpriseProspects.length} active enterprise conversations`);
```

### Exporting prospect data

Export prospects to CSV format via threads:

```typescript theme={null}
async function exportProspects() {
  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);

  // Build CSV rows
  const rows = allThreads.map((t) => ({
    externalId: t.prospect.externalId,
    platformId: t.prospect.platformId,
    username: t.prospect.username,
    displayName: t.prospect.displayName,
    bio: t.prospect.bio || "",
    followerCount: t.prospect.followerCount,
    verified: t.prospect.verified,
    tags: t.prospect.context.tags.join("; "),
    statusId: t.prospect.context.statusId || "",
    valuation: t.prospect.context.valuation || "",
    notes: t.prospect.context.notes || "",
  }));

  return rows;
}
```

## Platform profile data

Profile data is synced from X and includes:

| Field           | Description         | Updates             |
| --------------- | ------------------- | ------------------- |
| `username`      | @handle             | On each interaction |
| `displayName`   | Display name        | On each interaction |
| `bio`           | Profile bio         | On each interaction |
| `followerCount` | Followers           | Periodically        |
| `verified`      | Verification status | Periodically        |
| `image`         | Avatar URL          | On each interaction |

<Info>
  Profile data may be slightly stale. Follower counts and other stats are
  updated periodically, not in real-time. Check `isFresh`, `isStale`, and
  `confidence` to assess data quality.
</Info>

## Best practices

<AccordionGroup>
  <Accordion title="Store deal context in notes" icon="sticky-note">
    Use the notes field for context that doesn't fit structured fields:

    ```typescript theme={null}
    await client.patch(`/prospects/${id}/context`, {
      notes: `Meeting 1/15: Discussed pricing. Need to follow up with case study.
    Key decision maker. Budget approved Q1.`
    });
    ```
  </Accordion>

  <Accordion title="Track valuation for prioritization" icon="dollar-sign">
    Set valuation to prioritize high-value prospects:

    ```typescript theme={null}
    const sorted = threads
      .filter(t => t.prospect.context.valuation)
      .sort((a, b) =>
        (b.prospect.context.valuation ?? 0) - (a.prospect.context.valuation ?? 0)
      );
    ```
  </Accordion>

  <Accordion title="Assignees and done live on threads" icon="circle-user">
    `assigneeId` and `done` technically live on threads, not on the prospect.
    Tags, status, valuation, and notes live on the prospect. When you set
    `assigneeId` or `done` via the prospect update endpoint, it applies to **all
    threads** for that prospect on your team — so if you have multiple accounts
    talking to the same prospect, all of those threads are affected.
  </Accordion>
</AccordionGroup>

<Tip>
  For guidance on when to use tags vs statuses, see [Tags & statuses](/guides/tags-and-statuses).
</Tip>

## Next steps

<CardGroup cols={2}>
  <Card title="Lookup prospect" icon="search" href="/api-reference/prospects/lookup-prospect">
    GET /prospects/lookup
  </Card>

  <Card title="Update context" icon="pencil" href="/api-reference/prospects/update-prospect-context">
    PATCH /prospects/{id}/context
  </Card>

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

  <Card title="Managing threads" icon="messages-square" href="/guides/managing-threads">
    Work with conversations
  </Card>
</CardGroup>
