I run a visa agency in Bali, and the other half of my week goes into Apify Actors. The Pinterest one exists because of a question every content person eventually asks: for this keyword, what is actually winning, and which websites does Pinterest send the clicks to?
The "what is winning" half has a standard answer — pull the results, turn on the engagement fields, sort by saves. On 3 November 2026 I ran exactly that for three keywords and got 150 rows with saves: null in every one of them.
The run
Three keywords through Pinterest's own search, pins scope, 50 results each, 3 November 2026: bali villa, minimalist kitchen, fall outfits. Pinterest Search Scraper, no proxy, no login.
| Rows | 150 of 150 asked for, zero errors |
| Run time | 23.6 s |
| Pages fetched | 3 per keyword |
| Pins with an outbound link | 103 of 150 |
| Distinct link domains | 59, of which 46 appeared once |
| Rows with Pinterest topic tags | 64 of 150 |
saves / comments / shares
|
0 of 150 |
| Cost | $0.24 |
Then the same keyword again with the details switch on — 25 pins, the run that is supposed to go fetch saves, comments and shares from each pin's own page. Status message: details missing on 25 row(s). Twenty-five for twenty-five.
The ID stopped being a number
The detail lookup is one request per pin to Pinterest's own web endpoint, the same one its front end uses:
/resource/PinResource/get/?data={"options":{"id":"<pinId>","field_set_key":"detailed"}}
I fired it by hand with an id from the dataset and got this back:
{"resource_response":{"error":{
"status":"failure","http_status":404,"code":6432,"message":"Pin not found."},
"data":{"param_name":"pin","param_value":"e1_AfqPShzTvYDMaqIpMBCgj5Z2d_VIwI9_H7cQnwUIzYs8YAaZo4pt"}}}
e1_AfqPShzTvYDMaqIpMBCgj5Z2d_… — 55 to 57 characters of opaque token. Three weeks earlier the same field in my own README read 140806234512418. And that numeric id still works today: I replayed it against the same endpoint and it answered success, with aggregated_stats: {saves: 9971}. The endpoint is fine. The ids coming out of search are not ids any more.
They are not an encoding of one, either. The item's node_id is just base64 of Pin:e1_AfqPShz…. There are two numbers in the payload that look exactly like pin ids — AggregatedPinData:3927259812522011584 and story_pin_data_id: 3927259406614859136 — and both answer Pin not found. I also tried every field_set_key I know on the search endpoint; the stats never come down with the search result.
The token is at least stable and real. Three runs of the same keyword across ten minutes returned the same 50 pins with the same 50 ids in the same order, and https://www.pinterest.com/pin/e1_AfqPShz…/ serves a 200 and a real pin page — 1.5 MB of it, with the pin's own data no longer in the HTML. The page's embedded config names the experiment doing this: unauth_dweb_graphql_pin_page. Logged-out pin pages are moving to GraphQL with persisted queries, and the public REST shim that used to serve them is being starved.
What is still numeric
I probed the neighbouring endpoints before touching any code:
| Search scope / feed | ID that comes back |
|---|---|
| search, pins | e1_AfqPShzTvYDMaqIpM… |
| search, video pins | e1_AfqPShwGmvWYYcVIP… |
| search, boards | 283445438986959211 |
| search, people | 726487121048678985 |
| a profile's own pin feed | 516084438595318488 |
Only pins-out-of-search lost their id. Board followers, which also need a second request, still arrive: a 5-row board run with details on the same keyword filled all five, the largest at 3,847,062. Profile pin feeds are untouched, so my Pinterest Profile Scraper — which walks a user's pins rather than a search — never saw this at all. If you have a scraper reading Pinterest, the blast radius is exactly the search-to-pin-detail hop.
The fix is to stop asking
There is no clever recovery here, so the patch is small. Before it, a details run on 25 search pins fired 25 doomed requests, took twice as long as the plain run for nothing, and shipped saves: null, comments: null in every row — the empty-column failure mode I spend most of my schema work avoiding.
const numericId = (id) => /^\d+$/.test(String(id ?? ''));
const s = numericId(it.id) ? await pinStats(client, it.id) : null;
if (s) { row.saves = s.saves ?? row.saves; /* … */ }
else {
if (numericId(it.id)) stats.detailsFailed += 1; else stats.detailsUnavailable += 1;
// No numbers to add: leave the fields out instead of shipping empty columns.
dropUnfetched(row, 'pin');
}
Before the patch: 25 rows, 25 wasted requests, 7.3 s, details missing on 25 row(s), two always-null columns. After it: ten rows in 4.0 s, zero wasted requests, no saves/comments/shares keys in the row at all, and a status message that says what happened —
saves, comments and shares are not available for pins that come out of Pinterest search — Pinterest stopped giving those results a public pin id (10 row(s)); every other field is there, and boards and people searches still take details
Boards-with-details re-verified on the same build before the latest tag moved. A buyer who ticks a box that cannot work deserves a sentence, not a column of nulls — and the README now opens with that note instead of promising saves "one switch away".
So rank by what is left
reactions still comes down with every search result, free, no second request. It is a much smaller number than saves — median 15 on fall outfits, max 4,622 — but it is the only engagement signal search carries now.
And measuring it changed what I think the ranking question even is. Look at who occupies page one:
| bali villa | minimalist kitchen | fall outfits | |
|---|---|---|---|
| Median pin age | 144 days | 12 days | 21 days |
| Created in October 2026 | 11 of 50 | 43 of 50 | 31 of 50 |
| Median pinner followers | 157 | 1 | 11 |
| Pinners under 10 followers | 9 of 50 | 37 of 50 | 25 of 50 |
| Pins with zero reactions | 11 of 50 | 40 of 50 | 4 of 50 |
| Rows carrying topic tags | 38 of 50 | 7 of 50 | 19 of 50 |
For minimalist kitchen, Pinterest's first 50 results are a wall of three-week-old pins from accounts with a single follower and no reactions at all — one linking to quietnest.openlink.biz.id. That is not a niche's greatest hits, it is this month's publishing volume. bali villa behaves like a mature topic instead: pins back to 2022, a median pinner with 157 followers, tags on 38 of 50. Same endpoint, same page size, completely different thing in the box — and on a keyword like the first one, losing saves costs nothing, because there was nothing to sort.
The traffic half has its own trap
linkDomain is the field I actually use most: group 50 rows by it and you have the list of sites competing for a keyword. On fall outfits the naive grouping says the top destination is amazon.com with 9 of 26 links, a third of the traffic.
It is not. The same 26 links contain amazon.com ×9, amzn.to ×5, link.amazon ×3 and amzlink.to ×1 — 17 of 26, two thirds, under four different domain strings, because affiliate shorteners are domains too. Merge them and the second-placed site has one link. Pinterest's fashion search is an Amazon affiliate channel with a long tail, and the obvious GROUP BY understates it by half.
Two more things to fold into the same counting:
-
47 of 150 pins had no outbound link at all — 24 of 50 on
fall outfits. Pins uploaded straight to Pinterest point nowhere, so your traffic denominator is the linked subset, not the row count. - On
bali villa, 16 of 43 links go toinstagram.com. The largest single destination for that keyword is a profile you cannot study, which is a finding, not a bug.
Calling it
curl -X POST "https://api.apify.com/v2/acts/lergassy~pinterest-search-scraper/runs?token=$APIFY_TOKEN" \
-H 'content-type: application/json' \
-d '{"keywords":["fall outfits"],"searchType":"pins","maxItemsPerQuery":50}'
One other thing I measured while writing this: page_size: 25 is a suggestion. Pinterest answered my pages with 21 and 24 items, one of which each time was a story module — a carousel the front end renders, not a pin. Filter on type === 'pin' rather than trusting the count, and page with the bookmark the response hands back (2,064 characters of it) rather than an offset.
When not to use this. If you need saves, this is the wrong road entirely — go through a logged-in business account and Pinterest's official API, which is what the numbers were for. If you want everything a known creator ever pinned, search is the wrong entry point: walk the profile. And if you want one specific pin by URL, you need a pin reader, not a search.
Search is still the right tool for the two questions I started with. One of them now has a smaller answer, and the other one needed better arithmetic than I was doing.
Top comments (0)