Legal caveat: This guide covers access to public Pinterest data only. Scraping may violate Pinterest's Terms of Service, and unauthorized access to protected systems can implicate the U.S. Computer Fraud and Abuse Act (CFAA). In the EU, collecting personal data may trigger GDPR obligations. Always review Pinterest's Terms of Service and applicable laws before scraping, and prefer the official Pinterest API v5 for production workloads.
Building a visual-content or trend dataset often means you need to scrape Pinterest pins, board feeds, and search results. Pinterest is one of the largest visual discovery platforms, with over 518 million monthly active users as of Q1 2025, and its boards are a goldmine of structured trend data — product images, descriptions, links, and category tags. But Pinterest's anti-bot stack has matured significantly, and naive scraping fails quickly. This guide walks through how to scrape Pinterest pins and boards in 2026 using the platform's internal Resource API, rotating residential proxies, and proper request hygiene.
How to Scrape Pinterest Pins and Boards in 2026: The Landscape
Before diving into code, you need to understand what data is accessible and what isn't. Pinterest has two distinct data surfaces:
Public surfaces (no login required)
- Pin pages: Individual pin URLs like
pinterest.com/pin/123456789/render publicly with image, title, description, and outbound link. - Board feeds: Public boards display their pins to anonymous visitors.
- Search results: Keyword searches return pins and boards without authentication.
- User profiles: Public profiles show boards and pin counts.
Login-walled surfaces
- Home feed: The personalized recommendation feed requires authentication.
- Following feed: Content from accounts a user follows.
- Private boards: Secret boards are not accessible.
Official Pinterest API v5
Pinterest offers a REST API (v5) with endpoints for pins, boards, and analytics. It requires OAuth 2.0 and an approved developer app. Rate limits are documented at roughly 1,000 requests per 2-minute window for write operations and more generous limits for reads, but access to full pin metadata — especially for boards you don't own — is limited. For large-scale trend analysis or competitive research, the API alone often isn't sufficient, which is why teams turn to Pinterest API scraping via the internal Resource endpoints.
Understanding Pinterest's Internal Resource API
Pinterest's web frontend is a React single-page application that communicates with backend services through a set of internal endpoints collectively called the Resource API. These endpoints return JSON and are the backbone of every page you see. Key resources include:
/resource/PinResource/get/— fetches a single pin's full metadata./resource/BoardFeedResource/get/— paginated list of pins on a board./resource/SearchResource/get/— search results for a keyword query./resource/BoardResource/get/— board metadata (title, description, follower count)./resource/UserResource/get/— user profile data.
Each request is a GET (or sometimes POST) to https://www.pinterest.com/resource/{ResourceName}/get/ with two critical query parameters:
source_url=— the human-readable URL path that corresponds to the page (e.g.,/search/pins/?q=home%20decor).data=— a URL-encoded JSON object containing the resource-specific parameters (pagination cursor, query term, board ID, etc.).
Required headers
Pinterest's backend inspects several headers to validate requests. Missing or mismatched values trigger 403 responses or silent HTML redirects:
| Header | Purpose | Example |
|---|---|---|
X-Pinterest-PWS-Handler | Identifies the frontend handler that generated the request | boards/[username]/[slug].js |
X-APP-VERSION | Frontend build version; must match a current deploy | 9a8b7c6 |
csrftoken | CSRF token from the initial page load cookies | abc123def456 |
User-Agent | Must be a realistic, current browser UA | Chrome 130 on macOS |
Accept | JSON content type expected | application/json |
The X-APP-VERSION value changes with each frontend deploy. You can extract it from the initial HTML page (look for __PWS_INITIAL_DATA__ or script tags referencing versioned chunks). If your version is stale by more than a few days, requests start failing.
Anti-Bot Reality: Why You Need Rotating Residential Proxies
Pinterest employs per-IP rate limiting and behavioral bot scoring. Based on community testing and observable behavior, thresholds are roughly:
- ~50–80 requests per IP per minute before soft throttling (HTTP 429 or empty responses).
- ~150–200 requests per IP in a short burst before a temporary IP block (403 for 1–24 hours).
- Repeated blocks can lead to longer bans or permanent IP blacklisting.
Additionally, Pinterest localizes search results and recommendations. A search for "wedding centerpieces" from a US IP returns different pins than the same search from a German IP. If your dataset needs consistent, geo-specific results, you must pin your requests to a target country.
This is why a Pinterest scraper at any meaningful scale requires rotating residential proxies with geo-targeting. Datacenter IPs are flagged almost immediately by Pinterest's edge. Residential IPs blend with organic user traffic, and rotation distributes your request volume across many source IPs to stay under per-IP limits.
ProxyHat Setup for Pinterest Scraping
ProxyHat provides residential proxies with country and city-level geo-targeting and sticky sessions. For Pinterest, you'll want:
- Rotating residential IPs with
-country-US(or your target market) for localized search results. - Sticky sessions (
-session-) when you needcsrftokencontinuity across a pagination sequence. - Pacing — 1–3 second delays between requests per session to mimic human browsing.
Connection details:
# HTTP proxy (default)
http://USERNAME:PASSWORD@gate.proxyhat.com:8080
# With US geo-targeting
http://user-country-US:pass@gate.proxyhat.com:8080
# With sticky session for token continuity
http://user-country-US-session-abc123:pass@gate.proxyhat.com:8080
# SOCKS5 proxy
socks5://USERNAME:PASSWORD@gate.proxyhat.com:1080
You can explore ProxyHat's pricing and proxy locations to plan your setup. For broader context on proxy-based scraping, see our web scraping use case page.
Python Example: Scraping a Board Feed with Pagination
This example fetches pins from a public board using BoardFeedResource, paginating via the bookmark cursor. It uses ProxyHat residential proxies with a sticky session to maintain csrftoken continuity across the pagination sequence.
import requests
import json
import time
import urllib.parse
PROXY = "http://user-country-US-session-board1:pass@gate.proxyhat.com:8080"
PROXIES = {"http": PROXY, "https": PROXY}
HEADERS = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/130.0.0.0 Safari/537.36",
"Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "en-US,en;q=0.9",
"X-Pinterest-PWS-Handler": "boards/[username]/[slug].js",
"X-APP-VERSION": "9a8b7c6", # extract from initial page load
"Referer": "https://www.pinterest.com/",
}
BASE = "https://www.pinterest.com/resource/BoardFeedResource/get/"
# Example: board "username/spring-decor"
BOARD_ID = "123456789012345678"
SOURCE_URL = urllib.parse.quote("/username/spring-decor/", safe="")
def fetch_board_pins(board_id, max_pages=10):
pins = []
bookmark = None
# First request: get csrftoken from cookies
session = requests.Session()
session.proxies = PROXIES
# Prime the session with an initial page load
init_resp = session.get(
"https://www.pinterest.com/",
headers=HEADERS
)
csrf = session.cookies.get("csrftoken", "")
HEADERS["csrftoken"] = csrf
time.sleep(2)
for page in range(max_pages):
data_obj = {
"options": {
"board_id": board_id,
"page_size": 25,
"currentFilter": -1,
"field_set_key": "react_grid_pin",
},
"context": {}
}
if bookmark:
data_obj["options"]["bookmarks"] = [bookmark]
data_param = urllib.parse.quote(json.dumps(data_obj), safe="")
params = {
"source_url": SOURCE_URL,
"data": data_param,
}
resp = session.get(BASE, headers=HEADERS, params=params, timeout=30)
if resp.status_code != 200:
print(f"Page {page}: HTTP {resp.status_code}, stopping.")
break
payload = resp.json()
resource_response = payload.get("resource_response", {})
page_pins = resource_response.get("data", [])
if not page_pins:
print(f"Page {page}: no pins returned, end of board.")
break
for pin in page_pins:
pins.append({
"id": pin.get("id"),
"title": pin.get("title", ""),
"description": pin.get("description", ""),
"image_url": (
pin.get("images", {})
.get("orig", {})
.get("url", "")
),
"link": pin.get("link", ""),
"board_id": pin.get("board", {}).get("id", ""),
})
# Extract bookmark for next page
bookmark_list = resource_response.get("bookmark")
if bookmark_list and len(bookmark_list) > 0:
bookmark = bookmark_list[0]
else:
break
print(f"Page {page}: fetched {len(page_pins)} pins (total: {len(pins)})")
time.sleep(2) # pacing between pages
return pins
pins = fetch_board_pins(BOARD_ID, max_pages=10)
print(f"\nTotal pins scraped: {len(pins)}")
if pins:
print(json.dumps(pins[0], indent=2))
A single pin object from BoardFeedResource is large (often 2–5 KB of JSON). The truncated extraction above pulls the most useful fields. The full object includes rich metadata: close-up images, dominant color, repin count, video URLs (for video pins), and category.
Node.js Example: Scraping Pinterest Search Results
This Node.js example queries SearchResource for a keyword, using the ProxyHat HTTP gateway on port 8080 with US geo-targeting.
const axios = require('axios');
const querystring = require('querystring');
const PROXY_URL = 'http://user-country-US-session-search1:pass@gate.proxyhat.com:8080';
const HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'en-US,en;q=0.9',
'X-Pinterest-PWS-Handler': 'search/[scope].js',
'X-APP-VERSION': '9a8b7c6',
'Referer': 'https://www.pinterest.com/',
};
const BASE = 'https://www.pinterest.com/resource/SearchResource/get/';
async function searchPins(keyword, maxPages = 5) {
const allPins = [];
let bookmark = null;
const client = axios.create({
proxy: false,
httpsAgent: new (require('https-proxy-agent').HttpsProxyAgent)(PROXY_URL),
timeout: 30000,
});
// Prime session for csrftoken
const initResp = await client.get('https://www.pinterest.com/', { headers: HEADERS });
const cookies = initResp.headers['set-cookie'] || [];
const csrfMatch = cookies.find(c => c.startsWith('csrftoken='));
const csrf = csrfMatch ? csrfMatch.split('=')[1].split(';')[0] : '';
HEADERS['csrftoken'] = csrf;
await new Promise(r => setTimeout(r, 2000));
for (let page = 0; page < maxPages; page++) {
const options = {
query: keyword,
scope: 'pins',
page_size: 25,
};
if (bookmark) options.bookmarks = [bookmark];
const dataParam = encodeURIComponent(JSON.stringify({ options, context: {} }));
const sourceUrl = encodeURIComponent(`/search/pins/?q=${encodeURIComponent(keyword)}`);
const url = `${BASE}?source_url=${sourceUrl}&data=${dataParam}`;
try {
const resp = await client.get(url, { headers: HEADERS });
if (resp.status !== 200) {
console.log(`Page ${page}: HTTP ${resp.status}, stopping.`);
break;
}
const resourceResp = resp.data.resource_response || {};
const results = resourceResp.data || [];
if (!results.length) {
console.log(`Page ${page}: no results, end of search.`);
break;
}
for (const pin of results) {
allPins.push({
id: pin.id,
title: pin.title || '',
description: pin.description || '',
image_url: pin.images?.orig?.url || '',
link: pin.link || '',
});
}
const bookmarks = resourceResp.bookmark;
if (bookmarks && bookmarks.length) {
bookmark = bookmarks[0];
} else {
break;
}
console.log(`Page ${page}: ${results.length} pins (total: ${allPins.length})`);
await new Promise(r => setTimeout(r, 2000));
} catch (err) {
console.error(`Page ${page} error:`, err.message);
break;
}
}
return allPins;
}
searchPins('minimalist home office', 5)
.then(pins => {
console.log(`\nTotal pins: ${pins.length}`);
if (pins.length) console.log(JSON.stringify(pins[0], null, 2));
})
.catch(console.error);
Pagination, Sticky Sessions, and Fingerprint Hygiene
Bookmark/cursor pagination
Pinterest uses opaque bookmark strings as pagination cursors. The pattern is:
- First request: no bookmark in the
dataparam. - Response includes a
bookmarkarray (usually one element). - Pass that bookmark in
options.bookmarkson the next request. - When the response returns an empty
dataarray or a-end-bookmark, you've reached the end.
Bookmarks are session-specific. If you change your IP mid-sequence, the bookmark may become invalid. This is where sticky sessions matter.
Sticky sessions for csrftoken continuity
Use ProxyHat's -session- flag to keep the same exit IP for a pagination sequence. This ensures your csrftoken and IP remain consistent, which is what Pinterest expects from a real browser session:
# Sticky session for one board scrape sequence
http://user-country-US-session-board-abc123:pass@gate.proxyhat.com:8080
Rotate the session ID (e.g., board-abc123 → board-def456) between boards or search queries to get a fresh IP while maintaining continuity within a single logical operation.
Pacing
Human users don't fire 25 requests per second. Aim for 1–3 seconds between paginated requests and 5–10 seconds between different boards or searches. If you're running concurrent workers, keep total aggregate throughput under ~50 requests per minute per IP.
User-Agent and header hygiene
- Use a current, real browser User-Agent string. Check MDN's UA reference for format guidance.
- Keep
Accept-Languageconsistent with your geo-target (e.g.,en-US,en;q=0.9for US proxies). - Include a realistic
Referer— for board feeds, use the board URL; for search, use the search URL. - Update
X-APP-VERSIONregularly. Stale versions are a strong bot signal. - Don't send TLS fingerprints that mismatch your claimed UA. If using Python
requests, considercurl_cffiortls-clientto match Chrome's TLS fingerprint.
Common Mistakes and Edge Cases
- Stale
X-APP-VERSION: The most common failure. Pinterest deploys multiple times per week. Extract the version fresh before each scraping run. - Missing
csrftokenheader: The token must match the cookie. If you rotate IPs mid-session without a sticky proxy, the token becomes invalid. - Over-paginating: Some boards have 10,000+ pins. Set a
max_pagescap to avoid runaway requests and respect data minimization principles. - Ignoring
robots.txt: Check Pinterest's robots.txt before scraping. It specifies crawl-delay and disallowed paths. - Scraping personal data: Pin descriptions, comments, and user profile data may constitute personal data under GDPR. Stick to public, non-personal pin metadata (images, titles, links, board structure).
- Not handling 429s: Implement exponential backoff. A single 429 doesn't mean you're banned — it means you hit the rate limit. Back off for 30–60 seconds and retry with a new session.
Comparison: Resource API vs Official API v5 vs HTML Scraping
| Approach | Data Depth | Rate Limits | Auth Required | Reliability |
|---|---|---|---|---|
| Internal Resource API | Full pin metadata, images, links | Per-IP, ~50–80 req/min | Anonymous (csrftoken from page load) | Medium — breaks on frontend deploys |
| Official API v5 | Limited to owned/authorized boards | Documented, stable | OAuth 2.0 app | High — versioned, supported |
| HTML scraping | What's visible in rendered HTML only | Same per-IP limits | None | Low — breaks on any DOM change |
Ethical Scraping: When to Use the Official API Instead
Scraping Pinterest's internal Resource API exists in a gray zone. It's technically public data served to anonymous browsers, but automated access at scale may violate Pinterest's ToS. Here are guidelines for responsible practice:
- Collect only public, non-personal data. Pin images, titles, descriptions, links, and board structure are generally fair game. Avoid scraping user profiles, comments, or any data tied to identifiable individuals.
- Respect
robots.txt. Pinterest's robots.txt defines what crawlers are allowed to access. Honor crawl-delay directives. - Minimize data collection. Collect only what your use case requires. Don't archive full pin objects if you only need titles and image URLs.
- Use the official API for production. If you're building a commercial product, apply for Pinterest API v5 access. It's stable, documented, and won't break when the frontend deploys.
- Consider GDPR/CCPA implications. If pin descriptions contain personal data (names, emails, phone numbers), you may be processing personal data under EU/California law. Implement data retention limits and deletion procedures.
- Rate-limit yourself. Don't degrade Pinterest's service. Keep your request volume reasonable and back off on errors.
For SERP tracking and broader web-scraping workflows, see our SERP tracking use case and the ProxyHat documentation for proxy configuration details.
Key Takeaways
- Pinterest's internal Resource API (
BoardFeedResource,SearchResource,PinResource) returns rich JSON for public pins, boards, and search results without authentication — but requires correct headers (X-APP-VERSION,csrftoken,X-Pinterest-PWS-Handler).- Per-IP rate limits (~50–80 req/min) and bot scoring make rotating residential proxies with geo-targeting essential. Use
-country-USfor localized results and-session-for pagination continuity.- Pagination uses opaque bookmark cursors. Keep the same IP (sticky session) for an entire pagination sequence to avoid invalidating bookmarks.
- Pace requests at 1–3 seconds between pages. Update
X-APP-VERSIONregularly. Match your TLS fingerprint to your User-Agent.- For production systems, prefer the official Pinterest API v5. Use Resource API scraping for research, trend analysis, and datasets where the official API's data scope is insufficient.
- Always respect Pinterest's ToS, robots.txt, and applicable laws (CFAA, GDPR, CCPA). Collect only public, non-personal data.
FAQ
What is Pinterest Resource API scraping?
Pinterest Resource API scraping refers to calling Pinterest's internal JSON endpoints (like /resource/BoardFeedResource/get/ or /resource/SearchResource/get/) that the web frontend uses to load data. These endpoints return structured JSON for public pins, boards, and search results. You construct requests with a source_url parameter and a URL-encoded data JSON object, along with headers like X-APP-VERSION and csrftoken. It's more reliable than HTML scraping but requires maintaining correct headers and handling pagination via bookmark cursors.
Why do you need proxies to scrape Pinterest?
Pinterest enforces per-IP rate limits of roughly 50–80 requests per minute and uses bot scoring to detect automated traffic. Without proxies, a single IP gets throttled or blocked after a few hundred requests. Rotating residential proxies distribute your request volume across many source IPs, each appearing as organic user traffic. Geo-targeting (e.g., -country-US) is also important because Pinterest localizes search results and recommendations by region, so your data quality depends on the IP's geographic origin.
Which proxy type works best for scraping Pinterest?
Residential proxies are the best choice for Pinterest scraping. Datacenter IPs are quickly flagged by Pinterest's anti-bot systems. Residential IPs come from real ISPs and blend with organic traffic. Use rotating residential proxies with country-level geo-targeting for search scraping, and sticky sessions (-session-) for paginated board feeds where you need csrftoken and bookmark continuity. Mobile proxies also work well but are typically more expensive and better suited for high-trust scenarios.
How do you avoid blocks when scraping Pinterest?
To avoid blocks: (1) use rotating residential proxies with geo-targeting via ProxyHat (gate.proxyhat.com:8080); (2) pace requests at 1–3 seconds between paginated calls and 5–10 seconds between different boards or searches; (3) maintain header hygiene — current User-Agent, matching Accept-Language to your geo, fresh X-APP-VERSION; (4) use sticky sessions for pagination sequences to keep csrftoken valid; (5) implement exponential backoff on HTTP 429 responses; (6) keep aggregate throughput under ~50 requests per minute per IP.
Is scraping Pinterest legal?
Scraping public data from Pinterest may be permissible in some jurisdictions but can violate Pinterest's Terms of Service. In the US, the CFAA has been narrowed by the Van Buren ruling, but unauthorized access to protected systems remains risky. In the EU, collecting personal data triggers GDPR obligations. Always review Pinterest's ToS and robots.txt, collect only public non-personal data, and prefer the official Pinterest API v5 for production workloads. This guide is for educational purposes and does not constitute legal advice.






