# Linktree Scraper (profile links, bio, discovery by keyword) (`datahamster/linktree-profiles`) Actor

Linktree scraper for public linktr.ee profiles: every link with title, URL, type and position, plus the social icon row, bio, avatar, verified badge, category verticals and link count. Find profiles by keyword or by handle. No e-mail or phone harvesting — contacts are removed.

- **URL**: https://apify.com/datahamster/linktree-profiles.md
- **Developed by:** [Viktor Dubnytskiy](https://apify.com/datahamster) (community)
- **Categories:** Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 result items

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Linktree Scraper (profile links, bio, discovery by keyword)

Read any public **linktr.ee** profile as one flat row: every link on the page with its title, URL, link type and position, the social-icon row, the profile title, bio, avatar, verified badge and link count. Give it handles you already know, or a keyword — the actor finds the profiles for you. No login, no cookies, no API key.

### What you get

One row per profile: `id`/`username`, `url`, `title`, `description`, `avatarUrl`, `isActive`, `linkCount`, `links` (`title`, `url`, `type`, `position`), `socialLinks` (`platform`, `url`), `verified`, `tier`, `verticals`, `available`, `detailsFetched`, `query`, `rank`, `source`, `scrapedAt`.

### Example output

Real rows (`usernames: ["nba"]` and query `yoga studio`):

```json
{
  "id": "nba",
  "url": "https://linktr.ee/nba",
  "username": "nba",
  "title": "NBA",
  "description": "NBA ALL-STAR VOTING IS NOW OPEN. VOTE DAILY ON NBA.COM AND THE NBA APP",
  "avatarUrl": "https://ugc.production.linktr.ee/x4TCEyCTz6yyVcYZWCDa_wz2MeHI8Q0HitmMy",
  "isActive": true,
  "linkCount": 3,
  "links": [
    {"title": "Download the NBA App 📲", "url": "https://app.link.nba.com/DSkSiOcpgtb", "type": "CLASSIC", "position": 5},
    {"title": "Create Your NBA ID 🏀", "url": "https://app.link.nba.com/e/rV6Tw3sK7tb", "type": "CLASSIC", "position": 7},
    {"title": "Latest News 📝", "url": "https://app.link.nba.com/e/FFIFF4O4htb", "type": "CLASSIC", "position": 9}
  ],
  "socialLinks": [
    {"platform": "INSTAGRAM", "url": "https://instagram.com/nba"},
    {"platform": "X", "url": "https://x.com/nba"},
    {"platform": "TIKTOK", "url": "https://tiktok.com/@nba"},
    {"platform": "SNAPCHAT", "url": "https://www.snapchat.com/add/nba"},
    {"platform": "YOUTUBE", "url": "https://www.youtube.com/user/NBA"}
  ],
  "verified": true,
  "tier": "paid2",
  "verticals": ["sports"],
  "available": true,
  "detailsFetched": true,
  "source": "url"
}
```

A profile found by the keyword `yoga studio`, shortened:

| Field | Value |
|---|---|
| `url` | `https://linktr.ee/theyogarepublic` |
| `title` | `THE YOGA REPUBLIC` |
| `linkCount` | `7` |
| `links[4]` | `{"title": "OUR STORY 🖤", "url": "https://youtu.be/5ZASANDXryQ", "type": "YOUTUBE_VIDEO", "position": 4}` |
| `socialLinks` | `INSTAGRAM`, `X`, `FACEBOOK` |
| `verticals` | `["health-wellness"]` |
| `query` / `rank` | `yoga studio` / `5` |

### Use cases

- Take a list of Linktree handles from a campaign and pull every destination link behind them, with the link titles and the order they are shown in.
- Find the Linktree pages of a niche ("yoga studio", "coffee roaster berlin") and see which booking, shop and app platforms they actually send traffic to.
- Audit your own or a client's Linktree: check that every link still points where it should and that the order is right.
- Monitor mode: get an alert the moment a profile adds, removes or repoints a link, or changes its title.

### How it works

1. **Direct handles** — `usernames` accepts handles (`nba`, `@nike`) and full URLs (`https://linktr.ee/q.wellness`) alike.
2. **Discovery** — each keyword is searched in a public search index restricted to `linktr.ee`, so you get the profiles Linktree has published to the open web. Site sections (`/discover`, `/pricing`, …) and static assets are skipped.
3. **Reading** — every candidate's profile page is opened before it can become a row. Linktree server-renders the whole profile, so the links come out complete and in Linktree's own order, not scraped off a rendered screenshot.
4. **Filter** — `minLinks` and `verticalsAny` are applied after reading.
5. A handle with no Linktree profile comes back as `available: false` when you asked for it by name, and is dropped when it was only a dead link in the search index. A page that is not a profile page at all is reported as a block, never as an empty result.

### Input

| Field | Meaning | Default |
|---|---|---|
| `usernames` | Handles or linktr.ee URLs to read directly | empty |
| `queries` | Keywords to discover profiles with | empty |
| `resultsPerQuery` | Search hits scanned per keyword (1-200) | `30` |
| `minLinks` | Keep profiles with at least this many links | empty |
| `verticalsAny` | Keep profiles tagged with at least one of these Linktree verticals | empty |
| `maxItems` | Stop after this many rows | `50` |
| `mode` | `scrape` or `monitor` (only new/changed since last run) | `scrape` |
| `monitorKey`, `webhookUrl`, `telegramBotToken`, `telegramChatId` | Monitor-mode state key and alert targets | empty |

### Pricing

| Event | Price |
|---|---|
| result | $0.002 per profile ($2 per 1,000) |
| monitor-check | $0.005 per monitor run |
| change | $0.002 per new/changed profile |

Charged only for rows actually pushed. No proxy is needed, so platform usage stays small.

### Why this actor

- **Every link, not just the first few** — the whole `links` array with titles, types (`CLASSIC`, `YOUTUBE_VIDEO`, `APPLE_APP_STORE_MOBILE_APP`, …) and Linktree's own `position`.
- **Discovery by keyword**, not only by handle: you do not need the list of profiles up front.
- **Linktree's own category verticals** on every row (`health-wellness`, `independent-small-business`, `sports`, …), and a filter to keep only the categories you want.
- **Half the price** of the usual Linktree scraper, at $0.002 per profile.
- **Monitor mode** with webhook and Telegram alerts when a link is added, removed or repointed.
- **No contact harvesting.** See below — this is a deliberate design choice, and the reason this actor is safe to run on profiles that belong to individuals.

### No e-mail or phone extraction — on purpose

Other Linktree scrapers advertise "bio e-mail" extraction. This one does not do it, and cannot be configured to:

- There is no `email` and no `phone` field in the output schema.
- Contact links are **dropped whole** — they never appear in `links` or `socialLinks`. That covers `mailto:`, `tel:`, `sms:`, hosts that exist only to open a one-to-one chat (`signal.me`, `wa.link`), an international number parked anywhere in a URL (`t.me/+7999…`, `signal.me/#p/+1555…`), and the WhatsApp family (`wa.me/<number>`, `wa.me/c/<number>`, `wa.me/message/<code>`, `wa.me/qr/<code>`, `api.whatsapp.com/send?phone=…`). A **group invite is kept** — `chat.whatsapp.com/<code>` is a community, not a person.
- Contact details typed into the bio are **removed from `description`**: an e-mail address, or a dialable phone number (8+ digits starting with `0` or `+`, or a shorter group introduced by a phone word). Prices, dates, years and counts are left alone, so the bio still reads as the owner wrote it.

What you get instead is the structure Linktree publishes for every profile: who links to what, in which order, on which platforms. That is the part that is useful for market and traffic research — and the part that does not turn a scrape into a mailing list. If you need a contact, the profile's own links will take you to its website or booking page.

### Limits

- Discovery depends on a public search index: profiles never linked from the open web will not be found. Give the keyword in the language the profile is written in, and use `usernames` when you already know the handles.
- Linktree does not label a profile as a business or a person, so this actor does not guess either. It returns the same published fields for every profile and no contact data, which is what keeps a keyword run usable.
- Links behind a page password, an age gate or a "sensitive content" warning are not unlocked; such a profile returns whatever Linktree renders for a logged-out visitor.
- Header rows (a link entry with no URL) are not returned — they are labels, not links.
- `verticals` is Linktree's own tagging and is often absent; its `no-option` placeholder ("the owner picked nothing") is dropped rather than reported as a category, so an untagged profile comes back with an empty list.
- The bio scrub works on the shape of the digits, not on a list of keywords. A number written in words, or a contact hidden in an image, is out of its reach — and a very unusual bio may lose a long digit group that was not a phone number.
- Follower counts, click counts and analytics are private to the profile owner and are not available to any scraper.
- No members, no visitors, no message authors: profile-level facts only.

### FAQ

**Does it need a Linktree account or API key?** No. There is no credential field; every request is a plain public web request.

**Can it find profiles by keyword?** Yes — `queries` searches a public index restricted to `linktr.ee` and then opens every hit, so each row comes from a page that was actually read.

**Will it give me the e-mail address in the bio?** No, by design. See the section above.

**What happens when there are no results?** No rows are pushed and no result events are charged. The `RUN_SUMMARY` record in the run's key-value store carries `emptyReason`, which separates "the keyword has no profiles in the index" from "the source answered with a wall".

### Changelog

- 0.1: initial release — profiles by handle and by keyword, full link array with type and position, social icon row, verified badge, category verticals, `minLinks` and `verticalsAny` filters, monitor mode; contact links dropped and bio contacts scrubbed by design.

***

If this actor saved you time, a short review on its Store page genuinely helps other people find it. Found a bug or need a field that is missing? Open a ticket on the **Issues** tab.

# Actor input Schema

## `maxItems` (type: `integer`):

Stop after this many results (you are charged only for pushed items)

## `mode` (type: `string`):

scrape = full results; monitor = only new/changed items since the previous run of this task

## `monitorKey` (type: `string`):

Optional state key when not running as a saved task

## `webhookUrl` (type: `string`):

POST a change summary here in monitor mode

## `telegramBotToken` (type: `string`):

Optional: bot token for monitor-mode change summaries

## `telegramChatId` (type: `string`):

Optional: chat id that receives monitor-mode summaries

## `usernames` (type: `array`):

Profiles to read directly, one per line: "nba", "@nike" or "https://linktr.ee/q.wellness". Can be combined with Search queries or used alone.

## `queries` (type: `array`):

Keywords to find Linktree profiles for, one per line, e.g. "yoga studio", "coffee roaster berlin", "tattoo artist lisboa". Each keyword is searched in a public index restricted to linktr.ee. Any language works.

## `resultsPerQuery` (type: `integer`):

How many search hits to scan per keyword, e.g. 30 (about 7 per result page). More hits mean more profiles but a longer run; after de-duplication the number of profiles is usually lower. Range 1-200.

## `minLinks` (type: `integer`):

Keep only profiles with at least this many links, e.g. 3 to skip empty or abandoned pages. Leave empty for no lower bound.

## `verticalsAny` (type: `array`):

Keep only profiles tagged by Linktree with at least one of these category verticals, e.g. "health-wellness", "independent-small-business", "sports", "education". Case-insensitive. Profiles Linktree gives no vertical for are dropped while this filter is set. Leave empty for no category filter.

## Actor input object example

```json
{
  "maxItems": 50,
  "mode": "scrape",
  "usernames": [
    "nba"
  ],
  "queries": [
    "yoga studio"
  ],
  "resultsPerQuery": 30
}
```

# Actor output Schema

## `results` (type: `string`):

All pushed rows (dataset, JSON)

## `resultsTable` (type: `string`):

Dataset in the Console viewer

## `runSummary` (type: `string`):

RUN\_SUMMARY record

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "usernames": [
        "nba"
    ],
    "queries": [
        "yoga studio"
    ],
    "resultsPerQuery": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("datahamster/linktree-profiles").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "usernames": ["nba"],
    "queries": ["yoga studio"],
    "resultsPerQuery": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("datahamster/linktree-profiles").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "usernames": [
    "nba"
  ],
  "queries": [
    "yoga studio"
  ],
  "resultsPerQuery": 30
}' |
apify call datahamster/linktree-profiles --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datahamster/linktree-profiles"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/PJRoZhwYpd1oxSBDl/builds/b5icwVhWoxSGxMe6L/openapi.json
