# Bio-Link Scraper — Linktree, Beacons, Komi, link.me & More (`parsebird/biolink-scraper`) Actor

Scrape bio-link profiles from link.me, Linktree, Tap.bio, Komi, Pillar, bio.link, Beacons, and any other link-in-bio host into one unified shape: identity, links, socials, and on-page contact info.

- **URL**: https://apify.com/parsebird/biolink-scraper.md
- **Developed by:** [ParseBird](https://apify.com/parsebird) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.39 / 1,000 profiles

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### Bio-Link Scraper — Linktree, Beacons, Komi, link.me & More

**Bio-Link Scraper** turns any link-in-bio profile — [Linktree](https://linktr.ee), [link.me](https://link.me), [Beacons](https://beacons.ai), [Komi](https://komi.io), [Pillar](https://pillar.io), [bio.link](https://bio.link), [Tap.bio](https://tap.bio), or any other bio-link host — into one **unified row**: identity, bio, avatar, on-page contact email, social accounts, featured links, and every outbound link, regardless of which platform the creator used.

<table><tr>
<td style="border-left:4px solid #1C1917;padding:12px 16px;font-weight:600">
Native extractors for 7 major bio-link platforms plus a generic fallback for anything else — one consistent output shape no matter the source, so you never have to write platform-specific parsing.
</td>
</tr></table>

<br>

##### Copy to your AI assistant

Copy this block into ChatGPT, Claude, Cursor, or any LLM to start using this actor: Actor ID `parsebird/biolink-scraper` on Apify — call it with `ApifyClient` (`client.actor("parsebird/biolink-scraper").call(run_input={"profileUrls": ["https://linktr.ee/shopify", "https://link.me/danucd"], "maxItems": 100})`) — inputs are `profileUrls` (array of bio-link URLs, any mix of platforms) or `usernames` + `platform` (bare handles resolved against one platform: `linkme`, `linktree`, `tapbio`, `beacons`, `biolink`, `komi`, or `pillar`), plus `includeRawData` (boolean, default false), `maxItems` (default 100), and `maxConcurrency` (1–20, default 10) — output is one dataset row per profile with `platform`, `username`, `displayName`, `bio`, `avatar`, `cover`, `verified`, `isAdult`, `visitCount`, `followerCount`, `email`, `phone`, `location`, `websites[]`, `socialLinks[]`, `featuredLinks[]`, `links[]`, `totalLinks`, `theme`, `createdAt`, `updatedAt`, `scrapedAt` — API docs at `https://apify.com/parsebird/biolink-scraper/api` and API tokens at `https://console.apify.com/settings/integrations`.

### What does Bio-Link Scraper do?

This link-in-bio scraper reads each profile's real page data — the same JSON or HTML the platform itself renders — and normalizes it into one shape:

- 🔗 **7 native extractors**: link.me, Linktree, Tap.bio, Komi, Pillar, bio.link, and Beacons, each reverse-engineered to its actual data source (not screen-scraped guesswork)
- 🌐 **Generic fallback** for any other bio-link host — identity, socials, and visible links via JSON-LD and OpenGraph
- 📋 **Mix platforms freely** in one run, or scrape a whole batch of usernames against a single platform
- 📧 **On-page contact capture** — email and phone, only when the creator put them on the page themselves (no third-party enrichment, no guessing)
- 🔁 **One unified shape** every time: the same fields whether the source is a rich JSON API or plain HTML
- 🧯 A bad, private, deleted, or blocked profile is logged and skipped — it never stops the rest of the batch

### Coverage by platform

| Platform | Example profile URL | Coverage |
|----------|---------------------|----------|
| link.me | `https://link.me/danucd` | Full — identity, all links (featured links included), socials, on-page email |
| Linktree | `https://linktr.ee/shopify` | Full — identity, all links, socials |
| Tap.bio | `https://tap.bio/@nasa` | Full — identity, card links, socials |
| Komi | `https://kennyg.komi.io` | Full — identity, all module links, socials, theme |
| Pillar | `https://pillar.io/ninja` | Full — identity, links, socials |
| bio.link | `https://bio.link/art` | Full — identity, links, socials, on-page email |
| Beacons | `https://beacons.ai/defleppard` | Identity + full social list + featured link (see FAQ) |
| Any other bio-link host | `https://example.bio/name` | Generic fallback — identity, socials, visible links |

**Not supported**: search/discovery by keyword (this actor scrapes profiles you supply — it does not find creators for you), private/deleted/password-locked profiles, analytics or click counts per link (bio-link platforms don't expose these publicly), and non-bio-link URLs (a random website returns only whatever generic identity markup it exposes).

### Input parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| profileUrls | array of strings | one of these two | — | Bio-link profile URLs. Mix platforms freely — each is routed by host. |
| usernames | array of strings | one of these two | — | Bare handles, resolved against `platform` below (bulk-by-username mode). |
| platform | string | with `usernames` | `linkme` | Which platform the bare handles belong to: `linkme`, `linktree`, `tapbio`, `beacons`, `biolink`, `komi`, or `pillar`. Ignored for full `profileUrls`. |
| includeRawData | boolean | No | `false` | Attach the raw upstream payload (page JSON / GraphQL / JSON-LD) to each row for debugging or extra fields. |
| maxItems | integer | No | 100 | Hard cap on total dataset rows. |
| maxConcurrency | integer | No | 10 | Maximum profiles fetched in parallel (1–20). |
| proxy | object | No | off | Optional Apify Proxy for reaching bio-link hosts at high volume. |

#### Mixed platforms in one run

```json
{
  "profileUrls": [
    "https://link.me/danucd",
    "https://linktr.ee/shopify",
    "https://kennyg.komi.io",
    "https://bio.link/art"
  ]
}
```

#### Bulk by username (one platform)

```json
{
  "usernames": ["danucd", "someone", "another"],
  "platform": "linkme"
}
```

### Output example

```json
{
  "platform": "linkme",
  "profileUrl": "https://link.me/danucd",
  "username": "danucd",
  "displayName": "Dana",
  "bio": "ALL MY LINKS👇",
  "avatar": "https://media.link.me/_resize/image/quality=90,format=webp/images/webp-images/user-profile/1169288/tmp-2541-1763300314455.webp",
  "cover": null,
  "verified": false,
  "isAdult": false,
  "visitCount": "61.6k",
  "followerCount": null,
  "email": "dana.danucd@gmail.com",
  "phone": null,
  "location": null,
  "websites": [],
  "socialLinks": [
    { "title": "Instagram", "url": "https://www.instagram.com/danucd/", "type": "social", "network": "instagram", "handle": "danucd", "position": null }
  ],
  "featuredLinks": [
    { "title": "Winner Song", "url": "https://open.spotify.com/album/2Ly7LE7i3ebCLcYkwD9sng", "type": "social", "network": "spotify", "position": 8 }
  ],
  "links": [
    { "title": "Instagram", "url": "https://www.instagram.com/danucd/", "type": "social", "network": "instagram", "position": null }
  ],
  "totalLinks": 21,
  "theme": { "backgroundColor": "", "mainTextColor": "" },
  "createdAt": "2024-11-01 12:37:51",
  "updatedAt": "2025-11-16 13:43:17",
  "scrapedAt": "2026-09-27T13:39:34.085Z"
}
```

Every row is the same shape regardless of source platform — fields the source doesn't expose come back `null` or empty rather than missing. `featuredLinks[]` is link.me-only (the featured button links in the middle of the page); it's also included in `links[]`, which holds every outbound link on the page. Download the [full results](https://apify.com/parsebird/biolink-scraper) as JSON, CSV, Excel, HTML, or XML, or pull them with the [Apify API](https://docs.apify.com/api/v2#/reference/datasets).

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("parsebird/biolink-scraper").call(run_input={
    "profileUrls": ["https://linktr.ee/shopify", "https://link.me/danucd"],
    "maxItems": 100,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["platform"], item["username"], item["totalLinks"])
```

#### JavaScript

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('parsebird/biolink-scraper').call({
    profileUrls: ['https://linktr.ee/shopify', 'https://link.me/danucd'],
    maxItems: 100,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Use cases

- Build a lead list of creators/brands with their on-page contact email, straight from their bio-link page
- Monitor a competitor's or influencer's link-in-bio for new products, drops, or campaign links
- Migrate a creator's links from one bio-link platform to another without manual re-entry
- Enrich a creator database with social handles pulled from a single source of truth
- Audit which social platforms a roster of creators actually links from their bio page

### How it works

1. Give the Actor `profileUrls` (any mix of platforms) or `usernames` + a `platform` for bulk mode.
2. Each URL is routed by host to its native extractor — link.me, Linktree, Tap.bio, Komi, Pillar, bio.link, and Beacons each read from their real data source (an internal API, embedded page JSON, or the rendered HTML), not fragile CSS-only guessing. Anything else falls back to a generic JSON-LD/OpenGraph extractor.
3. Every platform's raw shape is normalized into the same UnifiedProfile fields, so downstream processing never needs to know which platform a row came from.
4. Each row is pushed to the dataset as soon as it's ready, so partial results are visible while the run is still going.
5. A failed, private, or blocked profile is logged and skipped — it never stops the rest of the batch.

### How much does it cost to scrape bio-link profiles?

| Event | Price per event | Price per 1,000 |
|-------|----------------|-----------------|
| profile-scraped | $0.00299 | **$2.99** |
| contact-found | $0.00179 | **$1.79** |

You're billed one `profile-scraped` event per profile returned in the dataset, and one additional `contact-found` event only when that profile exposes an on-page contact email. Pricing improves automatically with your Apify plan tier (Bronze $2.79/$1.69 per 1,000, Silver $2.59/$1.59, Gold $2.39/$1.49). Apify's free monthly platform credits cover a meaningful number of runs before you pay anything out of pocket.

### Input/Output

Set `profileUrls` or `usernames` from the Input tab in Apify Console, or pass them as JSON via the [API](https://docs.apify.com/api/v2). Results land in the run's default dataset — open the **Output** tab to preview rows, or export as JSON, CSV, Excel, HTML, or XML.

### Is it legal to scrape bio-link profiles?

Scraping publicly available data — the same identity, links, and contact info any visitor sees on a public bio-link page — for research, lead generation, or analysis is common practice, but always review each platform's Terms of Service for your specific use case and handle any captured contact information in line with applicable privacy law. Read more on [Apify's blog about the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

### FAQ

**Why does Beacons only return identity, socials, and one featured link?**
Beacons publishes identity, a full social list, and one featured link through its own JSON-LD block. Its multi-link button list loads from a private, authenticated API that isn't reachable without logging in as the page owner — so that part genuinely isn't available publicly. The row's `note` field flags this explicitly.

**What happens with a bio-link host you don't have a native extractor for?**
It falls through to the generic extractor, which reads JSON-LD, OpenGraph meta tags, and visible outbound links. Coverage is identity + socials + visible links — the `note` field flags these rows too.

**Why is `featuredLinks[]` only populated for link.me?**
It's the only platform in this set with a distinct "featured button" section separate from its full link list. Every platform's full link list (featured links included, where applicable) is always in `links[]`.

**Can I mix a bulk-username run with specific profile URLs?**
Yes — `profileUrls` and `usernames` can both be set in the same run; the Actor processes both sets (deduplicated).

**What's in `includeRawData`?**
The raw JSON, GraphQL response, or JSON-LD object the extractor parsed, attached as `rawData` on each row — useful for debugging or pulling a field this Actor doesn't map yet. Off by default to keep rows lean.

**Can I run this on a schedule?**
Yes — use Apify's built-in [Scheduler](https://docs.apify.com/platform/schedules) to re-check a roster of profiles daily or weekly.

**I found a bug or a profile that won't resolve — where do I report it?**
Open an issue on the Actor's **Issues** tab in Apify Console with the URL that failed, and it'll get looked at.

# Changelog

This Actor's version history is a separate document: https://apify.com/parsebird/biolink-scraper/changelog.md

# Actor input Schema

## `profileUrls` (type: `array`):

Bio-link profile URLs, one per line. Mix platforms freely — each is routed by host. Use this or 'usernames' below (not both).

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

Bare handles, resolved against the 'platform' below. Use this instead of 'profileUrls' for bulk-by-username scraping of a single platform.

## `platform` (type: `string`):

Which platform the bare handles in 'usernames' belong to. Ignored when using 'profileUrls'.

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

Hard cap on total dataset rows.

## `includeRawData` (type: `boolean`):

Attach the raw upstream payload (page JSON / GraphQL / JSON-LD) to each row for debugging or extra fields.

## `maxConcurrency` (type: `integer`):

Maximum profiles fetched in parallel.

## `proxy` (type: `object`):

Optional proxy for reaching bio-link hosts. Leave off to run direct; add Apify Proxy if you see blocked results at high volume.

## Actor input object example

```json
{
  "profileUrls": [
    "https://link.me/danucd",
    "https://linktr.ee/shopify",
    "https://tap.bio/@nasa",
    "https://kennyg.komi.io",
    "https://pillar.io/ninja",
    "https://bio.link/art",
    "https://beacons.ai/defleppard"
  ],
  "usernames": [],
  "platform": "linkme",
  "maxItems": 100,
  "includeRawData": false,
  "maxConcurrency": 10,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# 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 = {
    "profileUrls": [
        "https://link.me/danucd",
        "https://linktr.ee/shopify",
        "https://tap.bio/@nasa",
        "https://kennyg.komi.io",
        "https://pillar.io/ninja",
        "https://bio.link/art",
        "https://beacons.ai/defleppard"
    ],
    "usernames": [],
    "platform": "linkme",
    "maxItems": 100,
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("parsebird/biolink-scraper").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 = {
    "profileUrls": [
        "https://link.me/danucd",
        "https://linktr.ee/shopify",
        "https://tap.bio/@nasa",
        "https://kennyg.komi.io",
        "https://pillar.io/ninja",
        "https://bio.link/art",
        "https://beacons.ai/defleppard",
    ],
    "usernames": [],
    "platform": "linkme",
    "maxItems": 100,
    "proxy": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("parsebird/biolink-scraper").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 '{
  "profileUrls": [
    "https://link.me/danucd",
    "https://linktr.ee/shopify",
    "https://tap.bio/@nasa",
    "https://kennyg.komi.io",
    "https://pillar.io/ninja",
    "https://bio.link/art",
    "https://beacons.ai/defleppard"
  ],
  "usernames": [],
  "platform": "linkme",
  "maxItems": 100,
  "proxy": {
    "useApifyProxy": false
  }
}' |
apify call parsebird/biolink-scraper --silent --output-dataset

```

## MCP server setup

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

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/0YvBl6Wh6uLWEY5Ce/builds/aBerETkdgJgtS0xcE/openapi.json
