# Pinterest Profile Scraper — Bio, Links & Recent Pins (`afanasenko/pinterest-profile-scraper`) Actor

Scrape any public Pinterest profile by username — bio, website links, follower count, join date, boards and its most recent pins. Also finds profiles by keyword and enriches each one with the same detail. No login needed. Export to CSV, JSON or Excel, or run via API.

- **URL**: https://apify.com/afanasenko/pinterest-profile-scraper.md
- **Developed by:** [Andrey Afanasenko](https://apify.com/afanasenko) (community)
- **Categories:** Social media, Lead generation, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 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/platform/actors/running/actors-in-store#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

## Pinterest Profile Scraper — Bio, Links & Recent Pins

[![Apify Actor](https://apify.com/actor-badge?actor=afanasenko/pinterest-profile-scraper)](https://apify.com/afanasenko/pinterest-profile-scraper)

> **Give it a Pinterest username, get the whole account as a spreadsheet row.** Bio, every outbound link, follower count, the date the account joined, board count, verification badges — and, if you want them, its ten most recent pins.
> Built for the people who need the *website behind the account*: outreach teams building creator lists, brands checking who is pinning them, agencies qualifying partners before the first email.

> **🆓 Free to start:** no signup or card beyond your Apify account · press **Save & Start** with empty fields and it runs a small sample on a built-in account · Apify's Free plan includes $5 of monthly platform credit.

### 🎯 What it does

You give it usernames — or a keyword, and it finds the usernames itself. It reads Pinterest the way a logged-out visitor does and returns clean, deduplicated rows.

| Mode | Input | What each row is |
|---|---|---|
| **Mode 1: Get profiles by username** | usernames or profile links | an account: bio, website links, followers, join date, avatar, boards, verification flags |
| **Mode 2: Get a profile's recent pins** | usernames or profile links | one of that account's ten most recent pins: link, image, title, publish date |
| **Mode 3: Find profiles by keyword** | keywords | an account found for that keyword — the same full row as Mode 1, including bio and website |

The columns group into three themes: **identity** (handle, display name, bio, avatar, join date), **reach** (followers, board count, date of the last save, verification badges), and **destination** (every outbound link on the profile, plus the primary domain pulled out of them).

> ⚠️ **Pick your mode first.** Each mode reads only its own input field. **Example:** you select Mode 1 but type the username into the Mode 3 field. The actor runs Mode 1 — which never looks at Mode 3's field — finds nothing, and runs a small built-in sample instead of your lookup. **All three mode sections stay visible** whatever you pick; the Apify Console does not hide them, so fill in only the section whose label matches your mode.

### 📦 Output sample

One complete row from Mode 1, exactly as it lands in the dataset:

```json
{
  "result_type": "profile",
  "keyword": null,
  "username": "nasa",
  "full_name": "NASA",
  "profile_url": "https://www.pinterest.com/nasa/",
  "bio": "Explore the universe and discover our home planet with the official NASA boards on Pinterest. Visit us at www.nasa.gov",
  "website_urls": ["http://www.nasa.gov/"],
  "website_domain": "nasa.gov",
  "avatar_url": "https://i.pinimg.com/280x280_RS/37/a1/75/37a175e6d2431425576f0b8f81389394.jpg",
  "followers": 556119,
  "created_at": "2012-03-05T16:37:07.000Z",
  "board_count": 82,
  "last_pin_save_at": "2026-07-06T14:10:45.000Z",
  "is_verified_merchant": false,
  "is_ads_only_profile": false,
  "domain_verified": true
}
```

And one complete row from Mode 2:

```json
{
  "result_type": "pin",
  "profile_username": "marthastewart",
  "profile_url": "https://www.pinterest.com/marthastewart/",
  "position": 1,
  "pin_id": "32088216099525625",
  "pin_url": "https://www.pinterest.com/pin/32088216099525625/",
  "title": "Pin on Cleaning and Home-keeping Tips",
  "image_url": "https://i.pinimg.com/736x/fd/10/6b/fd106b822e4c88c5387a9e265883c0f7.jpg",
  "published_at": "2026-07-28T02:00:46.000Z"
}
```

Missing values are `null`, never the string `"N/A"`.

### ⚡ Quick start

1. Leave **Mode** on *Mode 1: Get profiles by username*.
2. Type one username per line into **Pinterest usernames** — `nasa`, `@nasa` and `https://www.pinterest.com/nasa/` all work.
3. Set **Max profiles** to something small for the first run, e.g. `10`.
4. Press **Save & Start**.

```json
{
  "operationMode": "profiles",
  "profilesUsernames": ["nasa", "marthastewart"],
  "profilesMaxItems": 10
}
```

Pressing **Save & Start** with everything empty is safe: the actor runs a small built-in sample instead of failing, and says so in the log. Whichever mode you select, fill in **that mode's** field — the other two are ignored.

### ⭐ Choose your mode

#### 🎯 Mode 1: Get profiles by username

The main one. You already know the accounts; this turns them into rows.

- **Input:** `profilesUsernames` — one per line. A bare handle, an `@handle`, or a full profile link.
- **Cost control:** `profilesMaxItems` — the run stops there. `0` means "no limit of your own"; the free plan still stops at 10 profiles.

#### 🎯 Mode 2: Get a profile's recent pins

What an account has published lately, as rows. Useful for checking whether a creator is still active, and what they post about, before you approach them.

- **Input:** `profilePinsUsernames` — same formats as Mode 1.
- **Cost control:** `profilePinsMaxItems` — counted in **profiles**, not pins. Each profile returns up to 10 pins.
- **Ceiling:** Pinterest exposes the ten most recent pins of a public profile and no more. This is a recency snapshot, not a full archive — an account with 4,000 pins still returns 10.

#### 🎯 Mode 3: Find profiles by keyword

Discovery. Pinterest returns roughly 50 accounts for a keyword; this mode then looks each one up, so the row carries the bio and the website links that a plain search never returns.

- **Input:** `findProfilesKeywords` — letters, digits and spaces only.
- **Cost control:** `findProfilesMaxItems` — counted across all keywords.
- **Worth knowing:** the follower and ads-only filters run **before** each account is looked up, so in this mode those two lower the bill as well as the output.

### 🧭 When to use it

- **Creator outreach.** Mode 3 on your niche with **Only profiles with a website link** turned on gives you a contactable shortlist with follower counts and last-save dates — enough to drop the dormant accounts before writing a single email.
- **Competitor and partner monitoring.** Mode 1 over a fixed list of handles, run weekly, shows follower movement and whether they are still posting.
- **Attribution checks.** `website_domain` tells you which site an account actually promotes, which is often not the one written in the bio text.
- **Content audits.** Mode 2 across your own accounts shows what each one published most recently, side by side.

### 🛠 Input

The input is grouped into sections. Only the section matching your selected mode is read.

| Field | Type | Mode | What it does |
|---|---|---|---|
| `operationMode` | select | all | Which of the three jobs to run. |
| `profilesUsernames` | list | 1 | Usernames, @handles or profile links, one per line. |
| `profilesMaxItems` | number | 1 | Stop after this many profiles. `0` = no limit of your own. |
| `profilePinsUsernames` | list | 2 | Same formats as Mode 1. |
| `profilePinsMaxItems` | number | 2 | Stop after this many **profiles**. Each returns up to 10 pins. |
| `findProfilesKeywords` | list | 3 | Keywords, one per line. Letters, digits and spaces only. |
| `findProfilesMaxItems` | number | 3 | Stop after this many profiles across all keywords. |
| `minFollowers` | number | 1, 2, 3 | Drop accounts below this follower count. |
| `onlyProfilesWithWebsite` | checkbox | 1, 2, 3 | Keep only accounts that publish at least one outbound link. |
| `websiteDomainContains` | text | 1, 2, 3 | Comma-separated. Keeps only accounts whose website matches one of them. |
| `excludeAdsOnlyProfiles` | checkbox | 1, 2, 3 | Drop accounts Pinterest marks as advertising-only. |

#### A note on usernames

A Pinterest handle is the part of the profile link between `pinterest.com/` and the next slash. It can contain dots, underscores and hyphens, and it is often not the display name: *A Beautiful Mess* is `abeautifulmess_`. Open the profile in a browser and copy the handle from the address bar — a guessed handle either finds nobody or, worse, finds somebody else.

A pin link or a board link will not work in these fields. Both point at content rather than at an account, and the actor says so instead of guessing.

### 💰 How much will scraping Pinterest profiles cost?

You are charged **once per profile**, not per row.

- Mode 1: one profile = one charge = one row.
- Mode 2: one profile = one charge = up to **ten** pin rows.
- Mode 3: one profile found and looked up = one charge = one row.

Never charged: a username that does not resolve to a public profile, a lookup that failed on our side, and — in Mode 3 — every candidate dropped by the follower or ads-only filter before it was looked up.

The website filters need the profile in hand, so they narrow the output rather than the bill. The follower and ads-only filters in Mode 3 do both.

### 🆓 Free vs paid plan

| | Free plan | Paid plan |
|---|---|---|
| Profiles per run | 10 (cap) | Unlimited — your **Max profiles** number is the only limit |
| Modes available | all three | all three |
| Fields per row | all of them | all of them |
| Filters | all of them | all of them |

Nothing is held back on the free plan except volume. When the cap binds, the run says so in `USER_MESSAGE` and records it in `FREE_LIMITS_APPLIED`.

### 🧪 Filter recipes

**A contactable creator shortlist**

```json
{
  "operationMode": "findProfiles",
  "findProfilesKeywords": ["sustainable fashion"],
  "findProfilesMaxItems": 50,
  "minFollowers": 5000,
  "onlyProfilesWithWebsite": true
}
```

`minFollowers` runs before each lookup here, so the accounts under 5,000 followers cost nothing at all.

**Everyone in a niche who sells on a given platform**

```json
{
  "operationMode": "findProfiles",
  "findProfilesKeywords": ["handmade jewelry"],
  "websiteDomainContains": "etsy,shopify"
}
```

**Are these accounts still alive?**

```json
{
  "operationMode": "profiles",
  "profilesUsernames": ["hgtv", "marthastewart", "sephora"]
}
```

Sort the output by `last_pin_save_at`. Anything older than a few months is a dormant account, whatever its follower count says.

### 📡 Live status & storage records

While a run is going, open the **Live Status** link on the run page for a self-refreshing view: profiles looked up, rows saved, rows dropped by filters, elapsed time and running cost.

| Record | When it appears | What is in it |
|---|---|---|
| `RUN_SUMMARY` | every run | mode, targets, profiles charged, rows saved, rows filtered, not-found count, upstream request count, cost, wall-clock seconds |
| `USER_MESSAGE` | when there is something to say | why a run returned nothing, which filter is binding, what to change next |
| `FREE_LIMITS_APPLIED` | free plan, cap reached | which cap applied and what it changed |
| `SKIPPED_ITEMS` | when something was skipped | entries that are not profile links, usernames with no public profile, duplicates, failed lookups — each with a reason |

### 🤖 Programmatic / API use

Start a run and get the dataset back in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/afanasenko~pinterest-profile-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "operationMode": "findProfiles",
        "findProfilesKeywords": ["sustainable fashion"],
        "findProfilesMaxItems": 50,
        "onlyProfilesWithWebsite": true
      }'
```

Read the run summary afterwards:

```bash
curl "https://api.apify.com/v2/actor-runs/$RUN_ID/key-value-store/records/RUN_SUMMARY?token=$APIFY_TOKEN"
```

Every output field is declared in the dataset schema, so agent frameworks can read the column list without a sample run.

### 💡 Tips & Best Practices

#### Getting the most out of a run

Copy handles from the address bar rather than typing them. Pinterest display names and handles diverge constantly, and a handle that does not exist returns nothing — correctly, and at no charge, but also with no data.

In Mode 3, one broad keyword beats three narrow ones. Pinterest matches account names and bios here, so `home decor` returns about 50 accounts where `mid century sage green home decor` returns almost none.

#### Cost control

Set **Max profiles** on the first run of any new list or keyword. Mode 3 will happily look up everything Pinterest offers.

In Mode 3, put your follower floor in `minFollowers` rather than filtering the spreadsheet afterwards — in that mode the filter runs before the lookup, so the accounts it drops are never paid for.

Mode 2 is the cheapest way to see recent activity: ten pins arrive for the price of one profile.

### ❓ FAQ

**Can I get an account's followers, or the accounts it follows?**
No. Pinterest does not expose follower or following lists to logged-out visitors, so no actor can return them honestly. The follower *count* is in every profile row.

**Can I get all of an account's pins, or its boards?**
Mode 2 returns the ten most recent pins, which is what Pinterest publishes for a public profile — there is no page two. Board contents are not available either; `board_count` tells you how many boards exist, not what is in them.

**Why is `board_count` empty on some rows?**
Those extra fields come from a second, confirming lookup that only runs when Pinterest can match the account under its own handle. The identity fields — bio, links, followers, join date — are always populated.

**Does it work on private accounts?**
No, and it will not pretend otherwise: a private, deactivated or renamed account is reported as "no public profile" and costs nothing.

**Can I schedule it?**
Yes. A weekly Mode 1 run over a fixed list of handles is the usual setup — follower movement and `last_pin_save_at` are the two columns worth watching over time. Use Apify's **Schedules** tab.

**How fresh is the data?**
Every run reads Pinterest live. Nothing is cached between runs.

### 🔗 Other related actors (same author)

| Actor | Domain |
|---|---|
| [Pinterest Scraper](https://apify.com/afanasenko/pinterest-scraper) | Pinterest |
| [Instagram Profile Scraper](https://apify.com/afanasenko/instagram-profile-scraper) | Instagram |
| [Instagram Related Profiles Scraper](https://apify.com/afanasenko/instagram-related-profiles-scraper) | Instagram |
| [TikTok Profile Scraper](https://apify.com/afanasenko/tiktok-profile-scraper) | TikTok |
| [YouTube Channel Scraper](https://apify.com/afanasenko/youtube-channel-scraper) | YouTube |
| [LinkedIn Jobs Scraper](https://apify.com/afanasenko/linkedin-jobs-scraper) | LinkedIn |
| [Zillow Scraper](https://apify.com/afanasenko/zillow-scraper) | US real estate |

### 🛟 Support & feedback

Open the **Issues** tab on this actor's Apify Store page. Bug reports that include the run ID get fixed fastest, and feature requests do get built.

### ⚖️ Is it legal to scrape Pinterest profiles?

This actor reads only what Pinterest shows a logged-out visitor: public profiles and their public pins. It does not log in, does not touch private accounts, and collects no email addresses or other private contact details — the only links it returns are the ones an account chose to publish on its own profile.

Public data collection is broadly lawful in the EU and the US, but you remain responsible for what you do with the output. Profile data about an identifiable person is personal data under GDPR: have a lawful basis before you store it, respect copyright in the images you find, and follow Pinterest's terms for any republication.

# Actor input Schema

## `operationMode` (type: `string`):

What this run should do. Each mode has its own section below.

## `profilesUsernames` (type: `array`):

One per line. A username (nasa), an @handle (@nasa) or a profile link all work. Pin links and board links do not — they point at content, not at an account.

## `profilesMaxItems` (type: `integer`):

Stop after this many profiles. 0 = no limit of your own (free plan still stops at 10).

## `profilePinsUsernames` (type: `array`):

One per line. Same formats as Mode 1: a username, an @handle or a profile link.

## `profilePinsMaxItems` (type: `integer`):

Stop after this many profiles — not pins. Each profile returns up to 10 pins. 0 = no limit of your own (free plan still stops at 10).

## `findProfilesKeywords` (type: `array`):

One per line. Letters, digits and spaces only — Pinterest search rejects punctuation, hashtags, emoji and non-Latin scripts.

## `findProfilesMaxItems` (type: `integer`):

Stop after this many profiles across all keywords. 0 = no limit of your own (free plan still stops at 10).

## `minFollowers` (type: `integer`):

Drop profiles below this follower count. 0 = off.

## `onlyProfilesWithWebsite` (type: `boolean`):

Keep only accounts that publish at least one outbound link on their profile.

## `websiteDomainContains` (type: `string`):

Comma-separated. Keeps only profiles whose website link matches one of these, for example: shopify,etsy

## `excludeAdsOnlyProfiles` (type: `boolean`):

Drop accounts Pinterest marks as advertising-only, which publish no organic pins.

## Actor input object example

```json
{
  "operationMode": "profiles",
  "profilesUsernames": [],
  "profilesMaxItems": 0,
  "profilePinsUsernames": [],
  "profilePinsMaxItems": 0,
  "findProfilesKeywords": [],
  "findProfilesMaxItems": 0,
  "minFollowers": 0,
  "onlyProfilesWithWebsite": false,
  "websiteDomainContains": "",
  "excludeAdsOnlyProfiles": false
}
```

# Actor output Schema

## `liveStatus` (type: `string`):

No description

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

No description

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

Aggregate run statistics — counts, mode, paid events fired, wall-clock time.

## `userMessage` (type: `string`):

Onboarding tip, paid-plan welcome, or diagnostic message — only present when the run has something to surface.

## `freeLimitsApplied` (type: `string`):

Free-tier caps that affected this run. Empty if no caps applied or user is on a paid plan.

## `skippedItems` (type: `string`):

Every username or keyword this run did not deliver, each with the reason: not a profile link, no public profile, duplicate, or a lookup that failed.

# 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 = {
    "operationMode": "profiles"
};

// Run the Actor and wait for it to finish
const run = await client.actor("afanasenko/pinterest-profile-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 = { "operationMode": "profiles" }

# Run the Actor and wait for it to finish
run = client.actor("afanasenko/pinterest-profile-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "operationMode": "profiles"
}' |
apify call afanasenko/pinterest-profile-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=afanasenko/pinterest-profile-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/tg8eWj0HBmaB5nD8X/builds/sgm9mL0fLJ1Gw7e5I/openapi.json
