# Pinterest Search Scraper: Pins, Images, Links & Pinners (`hyperbach/pinterest-search-scraper`) Actor

Scrape Pinterest search results by keyword or search URL, logged out: each pin's id and URL, title, description, image URL and size, video, the link it points to, date, reactions, hashtags, the pinner and the board. Several searches per run, a max per search, a free row per search with a verdict.

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

## Pricing

from $1.99 / 1,000 pins

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

## Pinterest Search Scraper: Pins, Images, Links & Pinners

**The pins of any Pinterest search, logged out: search queries or search URLs in, one row per pin out — the pin's id and URL, title, description and alt text, the link it points to and its domain, the image URL with its width and height, a 736-px and a 236-px copy, the video URL and length for a video pin, the date, the reactions count, the hashtags, the pinner's username, name, profile and followers, and the board's name, URL and pin count — several searches in one run, a max per search.** One free `search` row per input carries a completeness verdict: COMPLETE (Pinterest's own end of the list), CAPPED (your limit), TRUNCATED or PARTIAL. All Pins, Videos and Products searches. No login, no API key, no browser, proxies handled for you.

### Why this Pinterest search scraper

- **Several searches in one run.** Give search queries (`minimalist kitchen design`) or search URLs from your browser (`https://www.pinterest.com/search/pins/?q=garden%20ideas`, Videos and Products searches too): each search gets its own cap (`maxPins`), and the run's cap sits on top (`maxItems`).
- **The fields the search shows.** The pin's id and URL, title, description and alt text, the link it points to and its domain, the full-size image URL with its width and height plus a 736-px and a 236-px copy, the dominant colour, the video URL (HLS) and length for a video pin, the date it was created, the reactions count, the hashtags of its text, Pinterest's topics, the pinner's username, name, profile URL and follower count, and the board's id, name, URL and pin count.
- **Deep searches, checked.** Pins come 25 a call in Pinterest's own order, page after page by Pinterest's own cursor, to Pinterest's own end of the list: our test read 876 distinct pins for `garden ideas` in 44 pages (2026-10-22). About a fifth of a long search's results repeat earlier pins; each pin is delivered and charged once. The free `search` row says how many pins were delivered from how many pages and why the walk stopped: the list's end (COMPLETE, read twice to be sure), your cap (CAPPED), a list that only repeats itself (TRUNCATED) or a refusal or the time limit (PARTIAL).
- **Honest runs.** A search URL for boards or profiles, or a value that is not a Pinterest search, is a free `invalid` row; a repeated search a free `duplicate` row. Every input gets a row; a pin is charged once a run, also when two searches list it.
- **Fast and light.** Plain HTTP, the call Pinterest's own web app makes: 25 pins a call, about 2.9 KB on the wire a pin. 200 pins of two searches took 9 calls and 8.5 s on our test.

### Who it's for

- **Marketers and content creators** — what ranks for a keyword on Pinterest: the titles, descriptions, hashtags and images of the top pins, and the sites they link to.
- **E-commerce and SEO teams** — which domains and products the pins of a search point to, Products searches included, read again on a schedule to see what moved.
- **Researchers and data teams** — typed rows keyed by `pin_id`, with the board and the pinner, a free row per search with the verdict, and a run summary to check a pipeline against.

### Quick start

**The first 50 pins of a search**

```json
{
  "searchQueries": [
    "minimalist kitchen design"
  ],
  "maxPins": 50
}
```

**Three searches, 200 pins each**

```json
{
  "searchQueries": [
    "home office ideas",
    "garden ideas",
    "wedding cake"
  ],
  "maxPins": 200
}
```

**A Videos search by its URL**

```json
{
  "searchUrls": [
    "https://www.pinterest.com/search/videos/?q=garden%20ideas"
  ],
  "maxPins": 100
}
```

### Output

One row per pin (and one free search row per input):

| field | meaning |
|---|---|
| `row_type` | `pin` (one pin, charged) or `search` (the input's row with the walk's verdict; free). |
| `input_index` | The 1-based position of the input this row answers: `searchQueries` first, then `searchUrls`. |
| `input_value` | The input as you gave it (the query or the URL). |
| `input_kind` | Which input it came from: `query` (`searchQueries`) or `url` (`searchUrls`). |
| `query` | The search's words (a URL's `q=`). |
| `scope` | What the search reads: `pins` (All Pins), `videos` or `buyable_pins` (Products). |
| `search_url` | The search on Pinterest, as a URL you can open. |
| `status` | A search row: `ok`, `invalid`, `duplicate`, `failed` or `not_read` (see the FAQ). A pin row: `ok`. |
| `status_detail` | Why, in words, when the status is not `ok`. |
| `scraped_at` | When the row was read (ISO 8601, UTC). |
| `position` | The pin's place among this search's delivered pins, in Pinterest's order (1 = first). |
| `pin_id` | Pinterest's id of the pin, as the logged-out search gives it (mostly an encrypted `e1_…` id; the same in every run). |
| `pin_url` | The pin's page on Pinterest. |
| `title` | The pin's title (null when the pin has none). |
| `description` | The pin's description, as the pinner wrote it. |
| `alt_text` | The image's alt text (Pinterest's description of the picture). |
| `link` | The web page the pin points to (null for a pin uploaded with no link). |
| `domain` | The domain of the linked page (null when the pin has no link). |
| `image_url` | The full-size image. |
| `image_width` | The full-size image's width in pixels. |
| `image_height` | The full-size image's height in pixels. |
| `image_736x_url` | The image 736 pixels wide. |
| `thumbnail_url` | The image 236 pixels wide (the grid's thumbnail). |
| `dominant_color` | The image's dominant colour (`#rrggbb`). |
| `is_video` | True for a video pin. |
| `video_url` | A video pin's stream (an HLS `.m3u8` URL). |
| `video_duration_s` | A video pin's length in seconds. |
| `created_at` | When the pin was created (ISO 8601, UTC). |
| `reaction_count` | The pin's reactions, as Pinterest shows them (null when Pinterest gives no count). |
| `hashtags` | The hashtags in the pin's title and description, without the `#`. |
| `topics` | Pinterest's own topics for the pin (`home decor` …). |
| `is_promoted` | True for a promoted pin (an ad). |
| `pinner_username` | The pinner's Pinterest username. |
| `pinner_name` | The pinner's display name. |
| `pinner_url` | The pinner's profile on Pinterest. |
| `pinner_followers` | The pinner's follower count. |
| `board_id` | Pinterest's id of the board the pin is saved to. |
| `board_name` | The board's name. |
| `board_url` | The board's page on Pinterest. |
| `board_pin_count` | How many pins the board holds. |
| `pins_delivered` | A search row: the pin rows delivered for this input. |
| `pages_read` | A search row: how many pages of 25 results were read (the list's last page twice). |
| `repeats_skipped` | A search row: results skipped because Pinterest had shown the pin on an earlier page, or another search of the run delivered it. |
| `verdict` | A search row: `COMPLETE` (Pinterest's own end of the list, on two reads), `CAPPED` (your cap), `TRUNCATED` (the list only repeated itself for 5 pages, or 100 pages were read) or `PARTIAL` (a refusal or the time limit). |
| `verdict_detail` | A search row: the verdict in words — pins delivered, pages read, repeats, why the walk stopped. |

Example record:

```json
{
  "row_type": "pin",
  "input_index": 2,
  "input_value": "home office ideas",
  "input_kind": "query",
  "query": "home office ideas",
  "scope": "pins",
  "search_url": "https://www.pinterest.com/search/pins/?q=home%20office%20ideas",
  "status": "ok",
  "status_detail": null,
  "scraped_at": "2026-10-22T22:44:26Z",
  "position": 57,
  "pin_id": "e1_AfqPShz8pwffofn8A18P-osKgwfQ8UUTn8BcJ9QAs8THm9nGeafF",
  "pin_url": "https://www.pinterest.com/pin/e1_AfqPShz8pwffofn8A18P-osKgwfQ8UUTn8BcJ9QAs8THm9nGeafF/",
  "title": "Transform Your Space with DropTop by Pith & Stem, The Home Office Desk",
  "description": "Meet DropTop by Pith & Stem, the revolutionary folding workstation that blends style, functionality, and innovation. This wall-mounted desk folds up to reveal a stunning art print, …(truncated for display)",
  "alt_text": "a woman is standing in front of a window and holding onto the wall with her hands",
  "link": "https://pithandstem.com/shop/",
  "domain": "pithandstem.com",
  "image_url": "https://i.pinimg.com/originals/30/60/12/306012b654fb05a24dacaf081360ad68.jpg",
  "image_width": 1080,
  "image_height": 1920,
  "image_736x_url": "https://i.pinimg.com/736x/30/60/12/306012b654fb05a24dacaf081360ad68.jpg",
  "thumbnail_url": "https://i.pinimg.com/236x/30/60/12/306012b654fb05a24dacaf081360ad68.jpg",
  "dominant_color": "#686665",
  "is_video": true,
  "video_url": "https://v1.pinimg.com/videos/iht/hls/2b/f3/bf/2bf3bfd2b05dcf5f877170168ba561d3.m3u8",
  "video_duration_s": 52.5,
  "created_at": "2025-10-14T14:57:25Z",
  "reaction_count": 841,
  "hashtags": [],
  "topics": [],
  "is_promoted": false,
  "pinner_username": "pithandstem",
  "pinner_name": "DropTop™ WFH Smartdesks",
  "pinner_url": "https://www.pinterest.com/pithandstem/",
  "pinner_followers": 5066,
  "board_id": "815714619948164698",
  "board_name": "The Best Home Office Desk",
  "board_url": "https://www.pinterest.com/pithandstem/the-best-home-office-desk/",
  "board_pin_count": 38,
  "pins_delivered": null,
  "pages_read": null,
  "repeats_skipped": null,
  "verdict": null,
  "verdict_detail": null
}
```

### Pricing

**Pay for the pins a run delivers**, plus one run start; no platform usage is billed to you. The pin price falls with your Apify plan.

| event | unit | Free | Starter | Scale | Business |
|---|---|---|---|---|---|
| Pin (`pin`) | per 1,000 | $3.99 | $3.39 | $2.59 | $1.99 |
| Run start (`start`) | per run | $0.0012 | $0.0012 | $0.0012 | $0.0012 |

A pin is **$3.99 per 1,000** on the Free plan ($0.00399 a pin). Each run that delivers a charged pin also pays the **run start, $0.0012 once**; a run that delivers no charged pin pays nothing at all. So one search of 50 pins costs $0.2007 on the Free plan and $0.1707 on Starter, and several searches in one run share one start. The free `search` row of every input, a value that is not a search (`invalid`), a repeated search (`duplicate`), a search left at the cap or the time limit (`not_read`) and a refusal the run could not pass (`failed`) are never charged. A pin is charged once a run, also when several searches list it. A run that stops early (a timeout, an abort) is charged for the pins it delivered. `maxItems` counts the charged rows; before the first request the log names the price of each row at your plan and the most the run can cost. Enterprise plans have their own rates: the Actor's Pricing tab shows the price for your plan.

### Usage patterns

- **Queries and search URLs** — `searchQueries` takes the words you would type in Pinterest's search box; each reads the All Pins results. `searchUrls` takes a search URL as your browser shows it: `/search/pins/?q=…` (All Pins), `/search/videos/?q=…` (Videos) or `/search/buyable_pins/?q=…` (Products), on any Pinterest country domain. Boards and profiles searches are other records and are not read.
- **What a row is** — `row_type` = `pin`: one pin of the search, in Pinterest's order (`position`). `row_type` = `search`: the free row of one input value — `pins_delivered`, `pages_read`, `repeats_skipped`, the `verdict` with its `verdict_detail`, and the `status` with its reason.
- **How far a search goes** — `maxPins` (1 to 1,000, default 50) stops each search after that many new pins. A broad query ends at Pinterest's own end of the list, about 900 pins on our test; a narrow one can go on with looser matches. `maxItems` caps the charged rows of the whole run. The search row's `verdict` says where the walk stopped and why.

### Input configuration

| field | type | default | what it does |
|---|---|---|---|
| `searchQueries` | `array` |  | What to search for on Pinterest, one query a line (`minimalist kitchen design`), as you would type it in Pinterest's search box. Each query reads the All Pins results. Up to 200 searches a run (queries and URLs together). An empty query gets a free `invalid` row, a query given twice a free `duplicate` row. |
| `searchUrls` | `array` |  | Pinterest search URLs, as your browser shows them: `https://www.pinterest.com/search/pins/?q=garden%20ideas` (All Pins), `/search/videos/?q=…` (Videos) or `/search/buyable_pins/?q=…` (Products); any Pinterest country domain. A boards or profiles search URL, or a URL that is not a Pinterest search, gets a free `invalid` row. |
| `maxPins` | `integer` | `50` | Stop each search after this many pins (1 to 1,000). A pin Pinterest repeats on a later page, or one an earlier search of the run already delivered, is skipped and not counted. A broad query ends at Pinterest's own end of the list, about 900 pins on our test. |
| `maxItems` | `integer` | `0` | Stop the run after this many pin rows across every search (0 = no limit). Search rows are free and not counted; a search left unread at the cap gets a free `not_read` row. |

### Errors

**A run does not fail because of your input.** When an input cannot be used — a date that is not a date, an id in the wrong form, two settings that contradict each other — the run ends **Succeeded**, charges nothing, and says what to change:

- the status message starts with `INPUT REJECTED`;
- the dataset holds one row, `{"error": true, "code": "…", "message": "…"}`, and no results;
- the key-value store record `ERROR` holds the same object.

From code, check `error` on the first row before reading results.

A search URL for boards or profiles is not an error: it gets one free `search` row with `status` invalid that says why, and the run goes on with the others. A run that ends **Failed** is a fault on our side, never your input; it charges nothing, and we are alerted.

### FAQ

**Which status is which?**

A search row's `status` is `ok` (pins served), `invalid` (an empty query, a URL that is not a Pinterest search, or a boards or profiles search), `duplicate` (the same search again in the input: read once), `failed` (a refusal or an error; free) or `not_read` (left at `maxItems`, the charge limit or the run's time limit). A pin row's `status` is `ok`.

**Does it give the save count and the comments?**

No. Pinterest's logged-out search does not show a pin's save count or comment count, so the row does not carry them; it carries the reactions count Pinterest shows (`reaction_count`).

**Why does the pin id start with e1\_?**

Logged out, Pinterest names most pins by an encrypted id (`e1_…`) instead of the number you see when logged in. The id is the same in every run, and the pin URL (`pin_url`) opens the pin with it.

**Does it need a Pinterest account or API key?**

No. Everything is read from Pinterest's public search, logged out.

### Integration

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('hyperbach/pinterest-search-scraper').call({"searchQueries": ["minimalist kitchen design"], "maxPins": 50});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('hyperbach/pinterest-search-scraper').call(run_input={'searchQueries': ['minimalist kitchen design'], 'maxPins': 50})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

#### CLI

```bash
apify call hyperbach/pinterest-search-scraper --input '{"searchQueries": ["minimalist kitchen design"], "maxPins": 50}'
```

#### REST

```bash
curl -X POST "https://api.apify.com/v2/acts/hyperbach~pinterest-search-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' -d '{"searchQueries": ["minimalist kitchen design"], "maxPins": 50}'
```

### Support

apify@hyperbach.com

*This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.*

# Actor input Schema

## `searchQueries` (type: `array`):

What to search for on Pinterest, one query a line (`minimalist kitchen design`), as you would type it in Pinterest's search box. Each query reads the All Pins results. Up to 200 searches a run (queries and URLs together). An empty query gets a free `invalid` row, a query given twice a free `duplicate` row.

## `searchUrls` (type: `array`):

Pinterest search URLs, as your browser shows them: `https://www.pinterest.com/search/pins/?q=garden%20ideas` (All Pins), `/search/videos/?q=…` (Videos) or `/search/buyable_pins/?q=…` (Products); any Pinterest country domain. A boards or profiles search URL, or a URL that is not a Pinterest search, gets a free `invalid` row.

## `maxPins` (type: `integer`):

Stop each search after this many pins (1 to 1,000). A pin Pinterest repeats on a later page, or one an earlier search of the run already delivered, is skipped and not counted. A broad query ends at Pinterest's own end of the list, about 900 pins on our test.

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

Stop the run after this many pin rows across every search (0 = no limit). Search rows are free and not counted; a search left unread at the cap gets a free `not_read` row.

## Actor input object example

```json
{
  "searchQueries": [
    "minimalist kitchen design"
  ],
  "maxPins": 50,
  "maxItems": 100
}
```

# Actor output Schema

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

All scraped records in the default dataset. One row per pin (and one free search row per input):

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

How the run went: pin rows charged, free rows, inputs by status, the walk per search (pins delivered, pages read, repeated pins skipped, the verdict), the source counters (calls, served, refused, exits, rotations, bytes) and the memory watch.

# 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 = {
    "searchQueries": [
        "minimalist kitchen design"
    ],
    "maxPins": 50,
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("hyperbach/pinterest-search-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 = {
    "searchQueries": ["minimalist kitchen design"],
    "maxPins": 50,
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("hyperbach/pinterest-search-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 '{
  "searchQueries": [
    "minimalist kitchen design"
  ],
  "maxPins": 50,
  "maxItems": 100
}' |
apify call hyperbach/pinterest-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hyperbach/pinterest-search-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/uPfMgXhNSw2Rpph95/builds/Eqz7L3zsm4B5mfbKQ/openapi.json
