DEV Community

Cover image for Cross-Country Vinted Search: Comparing 5 Markets Without Burning Sessions
KazKN
KazKN

Posted on

Cross-Country Vinted Search: Comparing 5 Markets Without Burning Sessions

In Part 1, I showed why a Vinted monitor should track what changes, not just what exists.

This part is about a different problem:

What happens when the same query behaves differently across countries?

I ran this live on June 4, 2026 with Vinted Smart Scraper.

Disclosure: I built this Actor. Some Apify links may use my referral code.

๐Ÿงจ The mistake I almost made

The first tempting version of this feature was simple:

scrape 5 countries -> pick cheapest -> pick most expensive -> call it arbitrage
Enter fullscreen mode Exit fullscreen mode

That looks good in a screenshot, but it is not how reselling works.

A nike air force 1 listing in France and a nike air force 1 listing in Germany can differ by:

Difference Why it matters
Size EUR 35 and EUR 45 are not the same market
Condition "Good" can hide heavy wear
Photos Some listings are underpriced because they look bad
Seller reliability A cheap item from a weak seller is not always an opportunity
Shipping Country spread can disappear after fees
Demand A high listing price does not mean buyers pay it

So I do not want a fake "profit bot".

I want a country radar:

Where should I spend my monitoring budget today?
Enter fullscreen mode Exit fullscreen mode

That is the correct job for CROSS_COUNTRY.

๐ŸŒ The input

{
  "mode": "CROSS_COUNTRY",
  "query": "nike air force 1",
  "countries": ["fr", "de", "es", "it", "nl"],
  "condition": ["very_good", "good"],
  "priceMin": 20,
  "priceMax": 120,
  "sortBy": "newest_first",
  "maxItems": 15,
  "includePhotos": false,
  "maxConcurrency": 1,
  "requestDelayMs": 500
}
Enter fullscreen mode Exit fullscreen mode

I intentionally kept it small:

Setting Why
5 countries Current stability cap
15 max items 3 items per country
includePhotos: false Smaller output
maxConcurrency: 1 Slow but clean for a proof run

โœ… Live result

Field Value
Run ID 55PiTkUKZneTJo8Ep
Dataset ID 9I2fifqjEv9sQBmrg
Status SUCCEEDED
Duration 86s
Dataset rows emitted 1
Free-tier estimated charge $0.022

Why only one row?

Because CROSS_COUNTRY emits a comparison object instead of 15 individual listing rows.

That output shape is intentional.

If the Actor emitted every country listing as a normal row, a small five-country radar would quickly become noisy:

Output style What you get Problem
15 item rows Raw listings from all countries Harder to read in a webhook
5 country rows One row per market Better, but still requires aggregation
1 comparison row Best buy/sell country + spread + table Useful for scheduled decisions

For a recurring monitor, I care less about dumping every listing and more about answering:

Did the country spread become interesting enough to open a deeper monitor?
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“Š Output

{
  "query": "nike air force 1",
  "bestBuyCountry": "fr",
  "bestSellCountry": "de",
  "arbitrageSpreadPct": 47.4,
  "comparisons": [
    {
      "country": "fr",
      "avgPrice": 31.66,
      "medianPrice": 30,
      "minPrice": 25,
      "maxPrice": 39.99,
      "itemCount": 3,
      "currency": "EUR"
    },
    {
      "country": "de",
      "avgPrice": 46.67,
      "medianPrice": 50,
      "minPrice": 30,
      "maxPrice": 60,
      "itemCount": 3,
      "currency": "EUR"
    },
    {
      "country": "es",
      "avgPrice": 31.66,
      "medianPrice": 30,
      "minPrice": 25,
      "maxPrice": 39.99,
      "itemCount": 3,
      "currency": "EUR"
    },
    {
      "country": "it",
      "avgPrice": 31.66,
      "medianPrice": 30,
      "minPrice": 25,
      "maxPrice": 39.99,
      "itemCount": 3,
      "currency": "EUR"
    },
    {
      "country": "nl",
      "avgPrice": 41.67,
      "medianPrice": 30,
      "minPrice": 30,
      "maxPrice": 65,
      "itemCount": 3,
      "currency": "EUR"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Full country table from the run:

Country Items Min Median Avg Max
fr 3 25 30 31.66 39.99
de 3 30 50 46.67 60
es 3 25 30 31.66 39.99
it 3 25 30 31.66 39.99
nl 3 30 30 41.67 65

๐Ÿงช What the run actually proved

The useful part is not "Germany is profitable".

The useful part is:

Signal What I can say What I cannot say
France median was 30 France looked cheaper in this small sample Every French AF1 listing is cheap
Germany median was 50 Germany looked higher in this sample German buyers will pay more
Spread was 47.4% The query is worth deeper monitoring Net resale profit is 47.4%
Five countries finished successfully The workflow handled country redirects It will always finish at the same speed
One dataset row was emitted Schedule output is compact Internal scraping was only one request

This distinction matters because a reseller does not need another messy spreadsheet.

They need a short list of where to look next.

โš™๏ธ Why cross-country scraping is harder than it looks

Vinted is not one global marketplace with one clean catalog.

It behaves more like a group of country-specific markets that share branding but do not always behave the same way from a scraper perspective.

Technical issue Practical effect
Country redirects A request can land on the wrong country if session handling is weak
Different localized pages The same query can expose slightly different metadata
Anti-bot variance One country can be easy while another slows the whole run
Ranking drift newest_first today is not the same as tomorrow
Result caps A high maxItems across countries can multiply quickly
Currency/fees/shipping EUR price comparison is only the first layer

That is why I kept this test at:

5 countries
3 items per country
1 emitted comparison row
86 seconds total
Enter fullscreen mode Exit fullscreen mode

It is not the maximum possible configuration.

It is a clean proof that the workflow can survive a small scheduled country radar.

โœ… What this is good for

Use case Good fit?
Quick market spread check โœ…
Comparing the same query across countries โœ…
Building a shortlist of countries to monitor daily โœ…
Proving actual resale profit โŒ
Replacing human judgment โŒ

The output says:

Best buy country: fr
Best sell country: de
Spread: 47.4%
Enter fullscreen mode Exit fullscreen mode

That is not a profit guarantee.

It is a signal that this query may be worth monitoring more carefully.

โš ๏ธ The hidden trap

The dangerous version of this feature would be:

"Here is a profitable arbitrage gap."

That would be sloppy.

The safer version is:

"Here is a price spread from a small sample. Validate shipping, sizes, condition, fees, and actual sell-through before trusting it."

Signal Still missing
Country-level price spread Shipping and buyer behavior
Median/min/max Listing quality
5-country snapshot Time-series confidence
Query-level comparison Exact product matching

๐Ÿงญ The decision loop I would use

I would not schedule CROSS_COUNTRY as the final monitor.

I would use it as step one:

cross-country radar -> pick promising country -> sell-through tracker -> exact item watchlist
Enter fullscreen mode Exit fullscreen mode

Example:

Step Mode Goal
1 CROSS_COUNTRY Find which markets are unusually cheap/expensive
2 SELL_THROUGH_TRACKER Check if inventory actually disappears
3 PRICE_TRACK Watch exact URLs that are close to a buy decision
4 Webhook/Telegram/CSV Push only useful signals

The important part is that every step narrows the search space.

That is how you avoid turning a reseller tool into a bill generator.

๐Ÿ” Schedule templates

Small daily country radar:

{
  "mode": "CROSS_COUNTRY",
  "query": "nike air force 1",
  "countries": ["fr", "de", "es", "it", "nl"],
  "condition": ["very_good", "good"],
  "priceMin": 20,
  "priceMax": 120,
  "sortBy": "newest_first",
  "maxItems": 100,
  "includePhotos": false,
  "maxConcurrency": 1,
  "requestDelayMs": 500
}
Enter fullscreen mode Exit fullscreen mode

Weekly wider radar:

{
  "mode": "CROSS_COUNTRY",
  "query": "adidas spezial",
  "countries": ["fr", "de", "es", "it", "nl"],
  "condition": ["new_with_tags", "very_good", "good"],
  "priceMin": 15,
  "priceMax": 150,
  "sortBy": "newest_first",
  "maxItems": 300,
  "includePhotos": false
}
Enter fullscreen mode Exit fullscreen mode

Handoff to sell-through when a country keeps showing spreads:

{
  "mode": "SELL_THROUGH_TRACKER",
  "query": "nike air force 1",
  "countries": ["fr"],
  "maxItems": 100,
  "trackingStoreName": "daily-af1-fr-after-cross-country",
  "trackerId": "daily-af1-fr-after-cross-country",
  "emitRunSummary": true,
  "emitActiveItems": true
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ธ Cost guardrail

The live proof run emitted 1 row.

At the Free pricing available when I ran the test:

$0.020 / Actor start
$0.002 / result row
Enter fullscreen mode Exit fullscreen mode

That makes the live run roughly:

$0.020 + (1 * $0.002) = $0.022
Enter fullscreen mode Exit fullscreen mode

Important nuance:

maxItems controls how much the Actor is allowed to inspect
dataset rows control the result-row charge
Enter fullscreen mode Exit fullscreen mode

So a compact comparison output is useful for schedules, but you still need sane maxItems and country caps.

My default:

Frequency Suggested cap Why
First manual test 15 Fast proof
Daily radar 100 Enough signal, still controlled
Weekly scan 300 Wider sample without making it permanent
Always-on high frequency Avoid Use sell-through or exact watchlists instead

๐Ÿ” How I would schedule it

Use this weekly or daily:

{
  "mode": "CROSS_COUNTRY",
  "query": "nike air force 1",
  "countries": ["fr", "de", "es", "it", "nl"],
  "maxItems": 100,
  "sortBy": "newest_first"
}
Enter fullscreen mode Exit fullscreen mode

Then use SELL_THROUGH_TRACKER for the countries that repeatedly look interesting.

My rule:

CROSS_COUNTRY = find where to look
SELL_THROUGH_TRACKER = learn what moves
PRICE_TRACK = watch exact item URLs
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”” Webhook payload I would actually send

If I schedule this every day, I do not want the full dataset in Telegram.

I want a compact message:

{
  "workflow": "vinted-cross-country-radar",
  "query": "nike air force 1",
  "bestBuyCountry": "fr",
  "bestSellCountry": "de",
  "arbitrageSpreadPct": 47.4,
  "sampleSize": {
    "fr": 3,
    "de": 3,
    "es": 3,
    "it": 3,
    "nl": 3
  },
  "action": "open-sell-through-tracker-for-fr"
}
Enter fullscreen mode Exit fullscreen mode

That is the difference between scraping and monitoring.

Scraping gives you rows.

Monitoring tells you what to do next.

๐Ÿš€ Try it

Run the Actor here:

https://apify.com/kazkn/vinted-smart-scraper?fpr=8fp2od

Start with maxItems: 100.

If the comparison is useful, save it as an Apify Task and schedule it.

Top comments (0)