DEV Community

Matvey
Matvey

Posted on

25 of 25 Pinterest Pins Answered 'Pin Not Found'. Their IDs Had Stopped Being Numbers

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"}}
Enter fullscreen mode Exit fullscreen mode

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"}}}
Enter fullscreen mode Exit fullscreen mode

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');
}
Enter fullscreen mode Exit fullscreen mode

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 to instagram.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}'
Enter fullscreen mode Exit fullscreen mode

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)