URL: /code/data/social/find-handles · Id: `page:/code/data/social/find-handles`

# Handle.find

> List the accounts on one Venue that hold your buyers’ attention, by followers, engagement and topic, before a sponsorship or a guest spot.

POST https://api.sb/&#123;your-domain&#125;/handles/find

List the accounts on one Venue that hold your buyers’ attention, by followers, engagement and topic, before a sponsorship or a guest spot.

- **Price:** 60¢ per 100 accounts
- **Success rate:** 99.91% of 12,814 calls
- **Freshness:** Fresh for 7 days
- **Emits:** `Handle.found`

```text title="Run it · Ask"
Which YouTube channels with 10,000 to 100,000 subscribers explain rental finances to small landlords? Spend up to 10¢.
```

```ts title="Run it · SDK"
import { $ } from 'startup-builder'

const result = await $.try.Handle.find({ venue: 'youtube', topic: 'creators who explain rental-property finances to small landlords', followers: { min: 10000, max: 100000 }, limit: 10, maxCost: 0.1 })
```

```json title="Run it · Response"
{
  "handles": [
    {
      "venue": "youtube",
      "handle": "@smalllandlordmath",
      "name": "Small Landlord Math",
      "url": "https://www.youtube.com/@smalllandlordmath",
      "followers": 41200,
      "engagementRate": 0.046,
      "country": "US",
      "bio": "Spreadsheets, rent and the numbers behind owning 4 to 40 units.",
      "match": "both",
      "observedAt": "2026-10-27T14:02:11Z",
      "method": "sampled"
    }
  ],
  "after": "hdl_yt_2",
  "total": 57,
  "ignored": [],
  "cost": {
    "usd": 0,
    "maxUsd": 0.006,
    "units": 1,
    "misses": 0
  }
}
```

## Inputs
| Name | Type | Meaning | Price |
| --- | --- | --- | --- |
| `venue` | `'instagram' \| 'tiktok' \| 'youtube' \| 'x'` | Where the accounts are. Each Venue fills the same row; On each Venue says what each one adds. Required. | Included |
| `venue: 'youtube'` | Option | YouTube channels that match a term | About 10¢ per 100 searches; works 100% of 7,600 calls |
| `topic` | `string` | The accounts in one sentence: `creators who explain rental-property finances to small landlords`. Each row says whether it matched the topic, the filters or both. Selects accounts that post about a topic, described in a sentence. | About 7¢ per 100 accounts; works 100% of 3,251 calls |
| `keywords` | `string[]` | Short words that must appear in the bio, in the account’s own language: `landlord`, not `property management software`. | Included |
| `followers` | `Range` | Followers or subscribers, such as `{ min: 10000, max: 100000 }`: big enough to matter, small enough to answer. | Included |
| `followers.min: 10000` | Option | Accounts by follower range, engagement and country | About 60¢ per 100 accounts; works 99.91% of 12,814 calls |
| `engagementRate` | `Range` | Reactions and comments a post as a share of followers, 0 to 1: `{ min: 0.02 }` drops bought audiences. | Included |
| `countries` | `string[]` | Where the account is, by country, as ISO 3166-1 codes such as `US`. Default `your home Market`. | Included |
| `limit` | `number` | Accounts a page, at most 50. You pay for accounts returned, so keep it small while you tune filters. Default `10`. | Included |
| `after` | [`Cursor`](/code/data/types#cursor) | The cursor from the last page. | Included |
| `strict` | `boolean` | Refuse with `filter-unsupported`, at $0, instead of listing an unapplied filter in `ignored`. Default `false`. | Included |
| `maxCost` | `number` | The most this call may spend, in USD. Over it, the call is refused before it spends, or stops early with what it has. Default `0.25`. | Included |

## Outputs
| Field | Type | What `null` means | What it means |
| --- | --- | --- | --- |
| `handles` | `FoundHandle[]` | Never null: empty when nothing matched, at $0. | Under 0.01 at 50,000 followers or more: Most of the audience does not see or answer the posts. Skip it for a paid spot; measure the posts first with `Handle.measure`. *Planning estimate.*<br />`topic` only: The account talks about your problem but missed a hard filter, usually the follower range. Read why with `Handle.measure` before you drop it: a small account on topic often converts better than a large one off it. *Planning estimate.* |
| `handles[].handle` | `string` | Never null: `@smalllandlordmath`. | None. |
| `handles[].name` | `string \| null` | The account shows no display name. | None. |
| `handles[].url` | `string` | Never null. | None. |
| `handles[].followers` | `number`, in followers or subscribers | Never null. | None. |
| `handles[].engagementRate` | `number \| null`, in 0 to 1 | Fewer than 10 recent posts to average over. | None. |
| `handles[].country` | `string \| null` | The account names no country. | None. |
| `handles[].bio` | `string \| null` | The account has no bio. | None. |
| `handles[].match` | `'filters' \| 'topic' \| 'both'` | Never null: which read found it. `both` is the strongest row. | None. |
| `handles[]` | [`Observed`](/code/data/types#observed) | Each row also carries `observedAt`, `method` and `venue`. | None. |
| `after` | [`Cursor`](/code/data/types#cursor) | The last page: there are no more rows. | None. |
| `total` | `number \| null`, in accounts | The topic read reports no total. | None. |
| `ignored` | `string[]` | Never null: a filter the Venue does not take, such as `keywords` on a described search. | None. |
| `cost` | [`Cost`](/code/data/types#cost), in USD | Never null. In try mode `usd` is 0 and `maxUsd` is what the call would have cost. | None. |

### On each Venue
| Venue | What comes back | Limits | Price |
| --- | --- | --- | --- |
| Instagram | Accounts with followers, engagement rate and country; over 10,000 followers, the audience’s countries and ages too. | `country` is the country only: a city matches nothing. | None. |
| TikTok | Accounts with followers and engagement rate. | Words in the bio filter; words in captions are not read, so put topic words in `keywords`. | None. |
| YouTube | Channels with subscribers as `followers`, and engagement rate from recent videos. | Engagement rate is null for a channel with fewer than 10 recent videos. | About 10¢ per 100 searches |
| X | Accounts with followers and engagement rate. | The weakest Venue for a niche: reviews found mostly off-topic accounts for software topics; pair it with `topic`. | None. |
| LinkedIn | Nothing: no route lists LinkedIn members by audience. | Refuses with `venue-unsupported`; find the people who post on a topic with `Post.find` and take their authors. | None. |
| Reddit | Nothing: Reddit accounts hold no follower audience to list. | Refuses with `venue-unsupported`; read a community with `Post.find`. | None. |
| Hacker News | Nothing: Hacker News accounts hold karma, not an audience. | Refuses with `venue-unsupported`. | None. |
| Facebook | Nothing: no route lists pages by audience. | Refuses with `venue-unsupported`; measure a page you already know with `Handle.measure`. | None. |

## Effects
**Action class**

None: it reads, so no approval rule names it

**Modes**

- `try`: Runs the read and returns real rows; nothing is saved and nothing is billed.
- `send`: Returns a pending Action at once; its Event carries the rows when the read finishes.
- `do`: Waits for the read and returns the rows, billed per result.

**Idempotency**

The same `Idempotency-Key` with the same input replays the first answer at $0; with other input it returns 409.

**Saved or shared**

Nothing is written to your records. A list is shared across startups for the same filters and Venue for 7 days; it reads public accounts only.

**Emits**

`Handle.found` with `{ venue, handles, total }`

**Performed by**

[Partner Manager](https://startupbuilder.dev/agents/sales/partner-manager)

**Price**

About 60¢ per 100 accounts. Free: a page with no accounts, a refused filter. `maxCost` defaults to 25¢. Ten accounts on each of 3 Venues: 30 × 0.6¢ an account is about 18¢.

**Reliability**

The first route succeeds 99.91% of 12,814 calls. If it fails, the call refuses with `sources-busy` and nothing is spent.

**Freshness**

A reading younger than 7 days returns at $0. Readings are shared across startups for the same subject and locale.

**Batch and pages**

Up to 50 accounts a page. Pages of 25 rows by default, 100 at most; pass `after` for the next page. It answers in one response.

**Rate**

[The shared rate rules](/code/data#rate): 10 calls a second per key, and an array input counts as one call.

**Capability**

[Market data](https://startupbuilder.dev/systems/marketing/market-data)

## Refuses when
| Check | Error | Fix | Then call |
| --- | --- | --- | --- |
| A search engine, answer engine or network the call does not read, such as ChatGPT sent to `Keyword.measure` | [`venue-unsupported`](/code/refusals#venue-unsupported) | Pick one the call’s `venue` input lists; AI answers are `Keyword.ask`, posts are `Keyword.search`. | [`Keyword.ask`](/code/data/keywords/ask) |
| A call sent with `strict: true` and a filter no route can apply | [`filter-unsupported`](/code/refusals#filter-unsupported) | Drop that filter, or send without `strict` and read what comes back in `ignored`. | [`Organization.count`](/code/data/organizations/count) |

Every data call can also meet [the shared data-call refusals](/code/refusals#data-calls): [`input-missing`](/code/refusals#input-missing), [`limit-exceeded`](/code/refusals#limit-exceeded), [`max-cost`](/code/refusals#max-cost), [`rate-limited`](/code/refusals#rate-limited), [`sources-busy`](/code/refusals#sources-busy).

```json
{
  "type": "https://startupbuilder.dev/errors/venue-unsupported",
  "title": "The call does not read that search engine or network",
  "status": 422,
  "detail": "A search engine, answer engine or network the call does not read, such as ChatGPT sent to `Keyword.measure`.",
  "fix": "Pick one the call’s `venue` input lists; AI answers are `Keyword.ask`, posts are `Keyword.search`.",
  "next": {
    "verb": "Keyword.ask",
    "input": {
      "keyword": "best rent tracking software for a 40-unit property manager",
      "venues": [
        "chatgpt"
      ]
    }
  },
  "instance": "https://api.sb/leaseline.app/actions/action_1"
}
```

## When it waits
Returns in one response; nothing waits.

## Example
A refusal: LinkedIn lists no members by audience, so the call refuses at $0 and `next` names the read that finds who posts on the topic there.

**Ask**

```text
Which LinkedIn creators post about property management software?
```

**API**

```bash
curl -X POST "https://api.sb/leaseline.app/handles/find?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "venue": "linkedin",
  "topic": "people who post about property management software"
}'
```

**CLI**

```bash
npx startup-builder do Handle.find --data '{
  "venue": "linkedin",
  "topic": "people who post about property management software"
}'
```

The CLI runs in try mode unless you pass `--do`.

**MCP**

```json
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Handle.find({ venue: 'linkedin', topic: 'people who post about property management software' })"
  }
}
```

**SDK**

```ts
import { $ } from 'startup-builder'

const result = await $.try.Handle.find({
  venue: 'linkedin',
  topic: 'people who post about property management software',
})
```


```json title="Response, in try mode"
{
  "type": "https://startupbuilder.dev/errors/venue-unsupported",
  "title": "The call does not read that search engine or network",
  "status": 422,
  "detail": "Handle.find does not list LinkedIn members by audience.",
  "fix": "Find who posts on the topic with Post.find on LinkedIn, then measure each author with Handle.measure.",
  "next": {
    "verb": "Post.find",
    "input": {
      "term": "property management software",
      "venue": "linkedin"
    }
  },
  "instance": "https://api.sb/leaseline.app/actions/action_Li7wK2"
}
```

To retry, send the same `Idempotency-Key`: the retry returns the first answer for $0. A new key reads again.

A `sk_test_` key answers these the same way every time, at $0 ([every test value](/code/checks#test-values)).

| Test value | Always returns | What it is for |
| --- | --- | --- |
| `maxCost: 0.0001` | Refuses with [`max-cost`](/code/refusals#max-cost) | Any priced call with a maxCost of 0.0001 is refused before it spends: the price is above the cap. |
| `rate-limited.test` | Refuses with [`rate-limited`](/code/refusals#rate-limited) | Any call naming this hostname answers 429 with Retry-After, so your retry logic runs. |
| `sources-busy.test` | Refuses with [`sources-busy`](/code/refusals#sources-busy) | Any call naming this hostname answers 503 as if every route were busy, and nothing is spent. |

## Used by
None.

## Related
[`Post.find`](/code/data/social/find-posts) · [`Post.findEngagers`](/code/data/social/find-engagers) · [`Handle.measure`](/code/data/social/measure-handles)