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
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?
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
}
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?
๐ 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"
}
]
}
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
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%
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
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
}
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
}
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
}
๐ธ 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
That makes the live run roughly:
$0.020 + (1 * $0.002) = $0.022
Important nuance:
maxItems controls how much the Actor is allowed to inspect
dataset rows control the result-row charge
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"
}
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
๐ 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"
}
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)