ActorStack.dev
E-commerceLead generationDeveloper toolsv0.1.14updated 13 September 2026

Mercado Libre Scraper & API — 17 Countries

Every price carries its own currency.

Mercado Libre is not one marketplace but seventeen, and they do not behave alike: the large ones return thousands of results per query and have catalog product pages, while the small ones return dozens and have no catalog at all. This Actor reads all seventeen with one parser, and puts a currency on every single row because six of them mix the local currency and US dollars on the same results page.

oswaldocarabano/mercadolibre-data-actor

input.json
{
  "siteId": "MLV",
  "searchQueries": ["iphone"],
  "maxItems": 500,
  "scrapeDetail": false
}
Version
v0.1.14
Memory
4096 MB
Browser
yes
Proxy
Residential, exiting in each marketplace's country, required

Short answer

The Mercado Libre Scraper Actor extracts listings and product pages from 17 Latin American marketplaces with no API key, no account and no quota. Every row carries its own `currency` field, because Uruguay, Paraguay, the Dominican Republic, Nicaragua, Guatemala and Panama show local prices and `US$` prices on the same results page, and summing them without reading each row produces a meaningless number. Venezuelan product pages additionally return `price`, `price_local` and `exchange_rate_implied`, derived from the page's structured data rather than the rendered price. Brazil and Cuba are refused by the Actor rather than silently accepted. Pricing is $0.0045 per listing on the Free plan, falling to $0.0011 on Gold and above, with product pages billed separately at $0.018 because opening one costs about 40 times what a listing row costs.

Key points

  • Six of the seventeen marketplaces — Uruguay, Paraguay, the Dominican Republic, Nicaragua, Guatemala and Panama — show the local currency and US dollars on the same results page, which is why every row carries its own `currency` rather than the marketplace carrying one.
  • Venezuelan product pages return a dual price: `US$ 580.80` alongside `Bs. 476,314`, plus `exchange_rate_implied` derived from the page's structured data rather than from the rendered price, which splits the cents into a separate element and shifts the rate by 0.14%.
  • Three link shapes coexist on one results page — `articulo.…/MLA-…`, `/p/MLA…` and `/up/MLAU…` — so `item_id` is taken from the page's embedded search results array; a URL pattern silently drops the third shape, which can be 40% of a page.
  • Measured on 13 September 2026, Mercado Libre serves paginated and sub-category pages only under the `_NoIndex_True` URL form that its own robots.txt disallows by name: in Argentina, 3 of 3 attempts on the plain path returned the anti-bot wall against 3 of 3 pages served on the disallowed form.
  • A run that scraped nothing because every page was blocked fails instead of reporting success with zero rows, and every run reports `wall_hits`, `captcha_hits`, `retries` and two separate robots-policy counters.
  • Stock and sales figures are returned as the text the site serves — `+25 vendidos` — because Mercado Libre's own API answers `RANGO_1_50` for the same data, and no exact integer is invented from a range.
  • Brazil and Cuba are refused with a clear error rather than accepted: Brazil until there is a documented LGPD basis, Cuba because its proxy route is unverified.
On this page11 sections

What it does

Mercado Libre is not one marketplace but seventeen, and they do not behave alike: the large ones return thousands of results per query and have catalog product pages, while the small ones return dozens and have no catalog at all. This Actor reads all seventeen with one parser, and puts a currency on every single row because six of them mix the local currency and US dollars on the same results page.

output — one row
{
  "item_id": "MLV12345678",
  "title": "Apple iPhone 13 128 Gb Medianoche",
  "url": "https://articulo.mercadolibre.com.ve/MLV-12345678",
  "price": 580.8,
  "currency": "USD",
  "currency_symbol": "US$",
  "price_local": 476314,
  "currency_local": "VES",
  "exchange_rate_implied": 820.1,
  "condition": "Nuevo",
  "shipping_text": "Envío gratis",
  "link_kind": "publication",
  "is_catalog": false,
  "position": 3,
  "query": "iphone",
  "site_id": "MLV",
  "scraped_at": "2026-09-13T10:22:41Z",
  "from_cache": false,
  "data_age_hours": 0
}

Why this one

A currency on the row, not on the marketplace

Most marketplace scrapers attach one currency per country, which is correct in eleven of these seventeen markets and wrong in six. In Uruguay, Paraguay, the Dominican Republic, Nicaragua, Guatemala and Panama the local currency and `US$` appear in the same results page, so a column of prices summed under one currency label is a number with no meaning. Every row here carries `currency` and `currency_symbol` as served.

The Venezuelan dual price, and where the rate comes from

A Venezuelan product page shows both a dollar price and a bolívar price. Reading them off the rendered page gives the wrong ratio, because the rendered price splits the cents into a separate element — a 0.14% shift. The rate here is derived from the page's structured data instead, and across the product pages measured on one day it came out identical on every single one. Nobody else in this category returns the pair at all.

Two marketplaces wearing one brand

An `iphone` search returns 6,372 results in Mexico and 26 in Panama. The large markets have catalog product pages and installments; the small ones have neither, and one page is the entire result set with nothing to paginate. The Actor detects this and requests the next page only when the previous one came back full, so a query in Panama costs exactly one page load rather than a sequence of empty ones.

The robots.txt problem, stated instead of resolved quietly

Mercado Libre's robots.txt allows page 1 of a search and disallows both `/*_Desde_` and `/*_NoIndex_True`. The site now serves pagination and sub-categories only under that second form. So there is no polite pagination available today, and the Actor does not pretend otherwise: pages fetched against the policy are counted in `pages_disallowed_by_robots_fetched`, pages skipped for it in `pages_truncated_by_robots_policy`, and the operator chooses with `respectRobots`.

Six responses, one of which is data

The anti-bot wall answers HTTP 200 with a well-formed page and zero listings, which is indistinguishable from an exhausted page to anything that checks the status code. The Actor separates a page with data, an exhausted page, the wall, a CAPTCHA, a page that had not finished rendering, and a page returning a plausible but nearly empty result set. Only the first is treated as data, and a wall retires the proxy session rather than being retried on it.

The wall is absorbed, not billed

A blocked page costs a full browser load of 2.4 to 4.5 MB and returns nothing. Those are never charged, and neither is a product page that failed to open — if the detail fetch fails you get the listing row and pay the listing rate. Charging for attempts would make the wall rate the customer's problem instead of the Actor's.

Use cases

  • Track prices for a product category across several Latin American marketplaces with one comparable schema.
  • Measure how a brand is priced in Venezuela in both dollars and bolívares, using the dual price rather than an external exchange rate.
  • Build a competitor catalogue in one country, with seller, condition and shipping text as the marketplace serves them.
  • Compare marketplace depth between countries before deciding where a product is worth listing.
  • Feed a pricing model rows that each declare their own age through `from_cache`, `scraped_at` and `data_age_hours`.

Input

Every field has a default, and the defaults are deliberately small so a first run is cheap enough to inspect before you commit to a sweep. This table mirrors the Actor's own input schema field for field.

FieldDefaultWhat it does
siteIdstring"MLA"Country marketplacepersonal dataOne of the 17 supported marketplaces, from MLA (Argentina) to MPA (Panamá). MLB (Brasil) and MCU (Cuba) are refused rather than accepted.
searchQueriesstring[][]Search queriesFree-text searches, e.g. `iphone`. Page 1 returns up to 48 listings.
categoryUrlsstring[][]Category URLsCategory listing URLs to scrape directly, when you already know the category rather than the search term.
startUrlsobject[][]Start URLsListing or product URLs. A product URL is scraped as a detail page and billed at the detail rate.
maxItemsinteger100Max itemsHard cap on rows. The crawl stops as soon as the cap is reached, so pages already queued are not fetched.
scrapeDetailbooleanfalseScrape product detail pagespersonal dataOpens every product page for rating, reviews, location, stock ranges, attributes, seller and the dual price. Multiplies browser page loads by about 40 and is billed as a separate event.
allowPaginationbooleantruePaginate beyond the first pagepersonal dataFollows pagination in the `_NoIndex_True` form the site actually serves, which its robots.txt disallows by name. Every such page is counted in `pages_disallowed_by_robots_fetched`.
respectRobotsbooleanfalseRespect robots.txt strictlySkips every disallowed path, which in practice now means page 1 per query and little else. robots.txt is downloaded at the start of the run; if it cannot be read, the run fails rather than claim compliance it could not verify.
categorySlicingbooleantrueSlice queries by categorypersonal dataAlso fetches page 1 of each sub-category found on the results page. The only route past the ~2,000 item ceiling, and its coverage gain has not been measured.
maxSlicesinteger20Max category slices per queryUpper bound on sub-category pages per query, so slicing cannot quietly multiply a run.
maxConcurrencyinteger2Max concurrencyParallel browser pages. Each listing page is 2.4-4.5 MB, which is why the default is low.
useCachebooleantrueUse cacheReuses listings seen in the last 24 hours. Cached rows are charged normally and always declare `from_cache` and `data_age_hours`.

Output and fill rates

A field being in the schema is not the same as it having a value. The percentages below were counted on real runs; the sample sizes are in Measurements. Anything not listed here is not promised.

FieldFilledMeaning
item_idstring100%Primary key, cache key and dedupe key, taken from the page's embedded search results array rather than parsed out of the URL.
titlestring100%With `url` and `thumbnail`. Left in the marketplace's own language, because `Envío gratis` is data rather than interface.
pricenumber100%With `currency` and `currency_symbol`, per row and never per marketplace.
price_originalnumbernot measuredOnly present when the page shows a struck-through price.
seller_namestringnot measuredWith `condition`, `shipping_text` and `installments_text`. Fill rates vary widely by marketplace: the small markets have no installments at all.
link_kindstring100%With `is_catalog`. One of `publication`, `catalog` or `user_product` — the three link shapes that coexist on one results page.
positioninteger100%With `query` and `source_url`, so every row says where on which page it came from.
site_idstring100%With `scraped_at`, `from_cache` and `data_age_hours`. Every row declares its own age.
ratingnumbernot measuredWith `reviews_count`. Detail pages only — absent from listing pages on every marketplace.
sold_quantity_textstringnot measuredDetail pages only, returned as served: `+25 vendidos`. Mercado Libre's own API answers `RANGO_1_50` for the same data, so no exact integer is invented.
attributesobjectnot measuredWith `brand`, `sku`, `availability`, `breadcrumb` and `warranty_text`. Detail pages only.
price_localnumbernot measuredWith `currency_local` and `exchange_rate_implied`. The Venezuelan dual price, derived from structured data rather than the rendered price.

Every key is always present. A field that exists but is empty comes back as explicit null, so a parser never has to guess.

Datasets

Different record types go to different datasets, so the main table never carries columns that are blank on most rows.

  • defaultOne row per listing, in snake_case, with product text left in the marketplace's own language.billed
  • ERRORSFailed pages, kept separate so a parse failure never arrives mixed into the results.never billed
  • RUN_STATS (key-value store)Queries, pages fetched, items pushed and from cache, wall and CAPTCHA hits, retries, sessions retired, both robots counters, detail pages fetched and errors.never billed

Pricing

Pay per delivered result. Charges are applied as each row is produced rather than in a lump at the end, so an aborted run bills only for what it actually gave you.

EventPriceNotes
apify-actor-startActor start$0.00001Charged per gigabyte of run memory, minimum one event. Effectively free.
item-scrapedListing scraped$0.0045One listing delivered from a results page, on the Free plan. $0.0019 on Bronze, $0.0014 on Silver and $0.0011 on Gold and above. Pages blocked by the anti-bot wall are never charged, because the wall costs a full browser load and the Actor absorbs it.
item-detail-scrapedProduct page scraped$0.018One listing enriched with its product page — rating, stock ranges, attributes, seller and the dual price — on the Free plan. $0.014 on Bronze, $0.0115 on Silver and $0.0095 on Gold and above. Priced separately because opening a product page costs about 40 times what a listing row costs, and only charged when the page actually opened.

Measurements

Each figure is shown with the method that produced it. A benchmark without a method is a marketing claim wearing a number's clothes.

Marketplace depth

6,372 results in Mexico, 26 in Panama

The same `iphone` query measured across all 17 marketplaces. Argentina returned 5,078, Colombia 5,305, Venezuela 436 and Bolivia 27 — which is why pagination is requested only after a full page rather than by default.

Mixed currencies

6 of 17 marketplaces

Uruguay, Paraguay, the Dominican Republic, Nicaragua, Guatemala and Panama show local and US dollar prices in the same results page. Counted per marketplace across the same measurement pass.

Pagination against the wall

0 of 3 plain, 3 of 3 disallowed form

Measured in Argentina on 13 September 2026: `/iphone_Desde_49` returned the anti-bot wall on 3 of 3 attempts, `/iphone_Desde_49_NoIndex_True` served results on 3 of 3. The same split was measured in Mexico.

Derived exchange rate

identical on every product page measured

Venezuelan product pages on one day, comparing the rate derived from structured data against the rendered price. The rendered price splits the cents into a separate element and shifts the rate by 0.14%.

Query ceiling

about 2,000 items, roughly 39% of the total

Paginating an Argentine `iphone` search to the very end, against the result count the site itself reports for that query.

Listing page weight

2.4-4.5 MB per page

Measured across marketplaces. It is the reason the default concurrency is 2 rather than the platform default.

Catalog pages and installments

9 of 17 marketplaces

Argentina, Mexico, Colombia, Chile, Uruguay, Peru and Ecuador have catalog pages and installments; Venezuela, Bolivia, Paraguay, the Dominican Republic, Costa Rica, Nicaragua, Guatemala, Honduras, El Salvador and Panama have neither.

What it will not do

Stated plainly so you can judge fit before spending anything.

  • Brazil (MLB) and Cuba (MCU) are refused. Brazil is out until there is a documented LGPD basis; Cuba is unreviewed and its proxy route is unverified.
  • Roughly 2,000 items per query is a hard ceiling. Paginating an Argentine `iphone` search to the end reaches about 39% of what the site itself says it has.
  • `categorySlicing` is the only route past that ceiling, and how much extra coverage it gives has not been measured and is not promised.
  • Rating, reviews, location, stock ranges, attributes, seller id, description and the dual price appear on no marketplace's listing pages. They need `scrapeDetail`, which multiplies page loads by about 40.
  • Stock and sales figures are ranges, not numbers: `+25 vendidos` is returned as text, and any derived number is named `_min` because a minimum is what it is.
  • Buyer questions and reviews are out of scope entirely — that is text written by identifiable people.
  • Seller phone numbers and email addresses are never returned, and seller profile pages under `/perfil/` are never touched.
  • Coverage beyond page 1 varies by country and by run. In one pass Colombia and Chile returned the anti-bot wall for both URL forms.
  • One snapshot is one moment: cached rows are at most 24 hours old and every row says how old it is.

Privacy

  • No account, no session cookies, no OAuth and no API key: the Actor never signs in.
  • CAPTCHAs are never solved, by any method, and a sustained CAPTCHA rate stops the run.
  • Buyer questions and reviews are not scraped, because that is text written by identifiable people.
  • No seller phone numbers and no email addresses, which some Actors in this category do return.
  • Seller profile pages under `/perfil/` are disallowed by robots.txt and are never requested.
  • Images are not rehosted; rows carry image URLs.
  • Listings are cached for at most 24 hours and every row declares its age.
  • Removal requests, quoting the `item_id`: privacy@actorstack.dev

See also the data removal process.

Frequently asked questions

Do I need a Mercado Libre API key or a developer account?
No. The Actor needs no API key, no account, no cookies and no OAuth, and it never signs in. What it does need is a browser with a coherent fingerprint and a residential proxy that exits in each marketplace's own country, because plain HTTP, a Chrome TLS fingerprint and even a solved proof-of-work all end at the anti-bot wall.
Why does every row have its own currency field?
Because six of the seventeen marketplaces — Uruguay, Paraguay, the Dominican Republic, Nicaragua, Guatemala and Panama — show the local currency and `US$` in the same results page. A column of prices summed under a single per-country currency label is a number with no meaning, so the currency belongs to the row.
Can I get the price in both dollars and bolívares for Venezuela?
Yes, from product pages. A Venezuelan product page shows `US$ 580.80` and `Bs. 476,314`, and the Actor returns `price`, `price_local` and `exchange_rate_implied`. The rate is derived from the page's structured data rather than the rendered price, which splits the cents into a separate element and shifts the rate by 0.14%.
Why can't I get ratings and stock from the listing pages?
Because they are not there. Rating, reviews count, location, stock ranges, attributes, seller id and description appear on no marketplace's listing pages, so they require `scrapeDetail` and a product page load each. Competing Actors that promise those fields from listings return null for them.
Is pagination allowed by Mercado Libre's robots.txt?
No, and there is currently no version of polite pagination available. robots.txt disallows both `/*_Desde_` and `/*_NoIndex_True`, and the site now serves paginated and sub-category pages only under that second form. Turning on `respectRobots` keeps the run inside the allowed paths, which in practice means page 1 per query and little else.
How many results can one query return?
Roughly 2,000 items is a hard ceiling per query. Paginating an Argentine `iphone` search to the very end reached about 39% of what the site itself said it had. `categorySlicing` is the only route past that ceiling, and how much extra coverage it gives has not been measured and is not promised.
Am I charged for pages that get blocked?
Never. The anti-bot wall costs a full browser page load of 2.4 to 4.5 MB and returns nothing, and the Actor absorbs that cost. A product page that failed to open is not charged either — you get the listing row and pay the listing rate. You pay for delivered data, not for attempts.
Why does it refuse Brazil?
Brazil (MLB) is out until there is a documented LGPD basis for aggregating that data, and Cuba (MCU) is out because it has not been reviewed and its proxy route is unverified. The Actor refuses both inputs with a clear error rather than accepting them and returning something.
Does it return exact stock or sales numbers?
No, because Mercado Libre does not publish them. The site shows `+25 vendidos` and its own API answers `RANGO_1_50` for the same data, so the Actor returns the text as served in `sold_quantity_text`. Any number derived from a range is named with a `_min` suffix, because a minimum is what it is.
What happens if every page gets blocked?
The run fails. A run that scraped nothing because every page hit the wall reports failure instead of success with zero rows, which is the difference between a broken run and an empty market. Every run also reports `wall_hits`, `captcha_hits`, `retries` and `sessions_retired` so the rate is visible rather than inferred.

Guides for this Actor