Skip to main content

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.
{
  "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

FieldDescription
platformAlways "twitter" for now
platformIdThe X numeric user ID (string)
externalIdInbox internal ID. null if the prospect hasn’t been imported to the database yet
displayNameDisplay name shown on their profile
usernameHandle without @
handleHandle with @ prefix (e.g., @jordanrivera)
imageProfile picture URL from platform
imageNormalizedProfile picture URL normalized to highest quality — prefer this for display
bioProfile bio/description text
verified"none", "verified" (blue check), "business" (gold), or "government" (gray)
profileType"personal", "business", or "government"
followerCountNumber of followers (singular, not followersCount)
confidenceData accuracy score from 0 to 1 — higher is more reliable

Context fields

The context object contains your team’s custom data for this prospect:
FieldTypeDescription
tagsstring[]Array of tag IDs applied to this prospect
statusIdstring | nullPipeline status ID
valuationnumber | nullDeal value
notesstring | nullFree-text notes
threadsarray | nullThreads across all account links for this prospect
Tags, status, valuation, and notes are stored on prospects. Assignee and done/not-done technically live on threads — see Assigning and Marking Done for details.

Finding prospects

Lookup by platform ID

Find a prospect using their X user ID:
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");
}
Returns the prospect with context directly, or null if not found.
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.

Get by Inbox ID

Retrieve a specific prospect by their Inbox ID:
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.
Prospects are automatically created when they message you or when you start a conversation with them.

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

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:
// 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"],
});
There is no tagIds field. Tags are always modified incrementally with addTags and removeTags.

Setting status

// 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

// 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,
});
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.

Complete update

Multiple context fields can be updated in a single request:
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 CaseEndpointReturns
Get prospect profile + contextGET /prospects/lookupProspect with context (or null)
Find conversation threadGET /threads/lookupThread object (or 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:
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:
// 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:
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:
FieldDescriptionUpdates
username@handleOn each interaction
displayNameDisplay nameOn each interaction
bioProfile bioOn each interaction
followerCountFollowersPeriodically
verifiedVerification statusPeriodically
imageAvatar URLOn each interaction
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.

Best practices

Use the notes field for context that doesn’t fit structured fields:
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.`
});
Set valuation to prioritize high-value prospects:
const sorted = threads
  .filter(t => t.prospect.context.valuation)
  .sort((a, b) =>
    (b.prospect.context.valuation ?? 0) - (a.prospect.context.valuation ?? 0)
  );
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.
For guidance on when to use tags vs statuses, see Tags & statuses.

Next steps