# Back Market Spain Scraper — Refurbished Tech (`reventadata/backmarket-scraper`) Actor

Browses any Back Market Spain category and returns every product model with its starting price, the price when new, star rating and review count as separate columns. Optionally opens each product page for the full grade, battery, storage, colour and SIM price matrix.

- **URL**: https://apify.com/reventadata/backmarket-scraper.md
- **Developed by:** [reventadata](https://apify.com/reventadata) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

> **Unofficial.** This Actor is not affiliated with, authorised by, endorsed by, or in any
> way officially connected to Back Market. "Back Market" is a trademark of its respective
> owner and is used here only to describe what this tool reads. It reads the same public
> pages a browser does.

Back Market Scraper browses any category on
[backmarket.es](https://www.backmarket.es) — the Spanish storefront of Europe's largest
refurbished-electronics marketplace — and returns every product model with its
**starting price, the price the device cost new, star rating and review count as separate
columns**.

### What data can you extract from Back Market?

A Back Market category page lists one card per product *model* — "iPhone 16" — not one per
merchant offer. Each card quotes a **starting-from** price, the cheapest of that model's
variants, next to what the device cost new. This Actor returns that comparison as data:
`price`, `priceWhenNew`, `rating` and `ratingCount` are first-class fields, so a whole
category becomes a table you can sort by discount rather than a page you have to scroll.

Turn on `fetchProductDetails` and each model's product page adds its full **variant price
matrix** — every condition grade, battery-health band, storage size, colour and SIM type
with its own price.

### Why scrape Back Market?

- **Discount as a number.** `priceWhenNew` comes straight off the listing card, so
  "587 € against 859 € new" is two columns rather than a sentence.
- **The variant matrix, when you want it.** `grades`, `batteryOptions`, `storageOptions`,
  `colorOptions` and `simOptions` each carry a label, the site's own value and a price —
  the whole price ladder for a model in one row.
- **Category browsing, by design.** backmarket.es's `robots.txt` reserves its search
  endpoint (`Disallow: */search`, and `*/l/sort` and `*/l/filters` with it) while leaving
  category browse pages and product pages open. Category browsing is therefore the only
  surface this Actor requests, and free-text search is not offered at all. Point it at a
  category and it walks the catalogue the way the site publishes it.
- **Honest about what it cannot narrow.** The site's own filter controls do not work over
  HTTP — see *Notes and limits*. Rather than ship a control that quietly does nothing,
  this Actor applies price and brand bounds to the rows it fetched and says so.
- **No personal data at all.** Back Market names no merchant anywhere on these pages. See
  Privacy below.

### How to scrape Back Market

1. Open a category on the site and copy the whole URL from the address bar — for example
   `https://www.backmarket.es/es-es/l/iphone/aabc736a-cb66-4ac0-a3b7-0f449781ed39`. The
   slug alone is not enough: a category is addressed by its slug **and** the UUID after it.
2. Put one or more of those URLs in `categories`.
3. Optionally narrow with `minPrice`, `maxPrice` or `brands`.
4. Optionally turn on `fetchProductDetails` for the variant price matrix.
5. Run it. Results land in the dataset.

#### Input

| Field | Type | Meaning |
|---|---|---|
| `categories` | array | One or more category URLs. Required. |
| `minPrice` / `maxPrice` | integer | Optional, whole euros, against the starting-from price. Applied after fetching — see the note on filters. |
| `brands` | array | Optional. The site's own lowercase brand values, e.g. `apple`. Applied after fetching. |
| `fetchProductDetails` | boolean | Open each product page for the variant price matrix. Off by default. |
| `maxItems` | integer | Hard cap on rows pushed. Default 300. |
| `maxPages` | integer | Pages per category, clamped to the site's window of 33. |

```json
{
    "categories": [
        "https://www.backmarket.es/es-es/l/iphone/aabc736a-cb66-4ac0-a3b7-0f449781ed39"
    ],
    "minPrice": 300,
    "maxPrice": 700,
    "maxItems": 300
}
```

#### Output

```json
{
    "id": "5119391",
    "title": "iPhone 16",
    "url": "https://www.backmarket.es/es-es/p/iphone-16",
    "category": "iphone",
    "brand": "apple",
    "productType": "Smartphone",
    "model": "iphone 16",
    "variationId": "3e568013-bb85-4f6d-a51b-ab9171500bfa",
    "price": 587,
    "priceWhenNew": 859,
    "currency": "EUR",
    "isStartingFromPrice": true,
    "rating": 4.6,
    "ratingCount": 14084,
    "availableColors": ["Azul ultramar", "Blanco", "Negro", "Rosa", "Teal", "Ultramarine", "Verde azulado"],
    "imageUrl": "https://www.backmarket.es/cdn-cgi/image/..."
}
```

With `fetchProductDetails` on, each row also carries `currentPrice` and the five option
lists — `grades`, `batteryOptions`, `storageOptions`, `colorOptions` and `simOptions`:

```json
{
    "currentPrice": 587,
    "grades": [
        { "label": "Correcto", "value": "12", "price": 657, "selected": false },
        { "label": "Muy bueno", "value": "11", "price": 620, "selected": false },
        { "label": "Excelente", "value": "10", "price": 587, "selected": true }
    ]
}
```

### Notes and limits

- **`price` is a starting-from price, not the price of a thing you can buy.** A card
  quotes the cheapest variant of the model — the site labels it *Desde* — and
  `isStartingFromPrice` records that. The actual price depends on grade, battery, storage,
  colour and SIM, which is exactly what `fetchProductDetails` returns.
- **One row is one product model, not one merchant offer.** Back Market sells through
  merchants, but its listing and product pages publish a single price per variant
  combination rather than a list of competing offers. There is no deeper layer to reach.
- **1,000 items per category, and that is the site's limit, not this Actor's.**
  backmarket.es serves at most 32 pages of 31 models plus a partial page of 8 for any one
  category, no matter how large the category really is. Past that the site returns no
  cards **and its own product count flips to `0 productos`** — measured on a category that
  reported 5,888 products one page earlier. That forged empty result is indistinguishable
  from a real one except by the page number, so this Actor checks the window first and
  stops there rather than fetching the lie. **A category larger than 1,000 items is not
  fully covered by one run**; narrow it to a sub-category and run each one.
- **The site's own filters cannot be used by any HTTP client, and this Actor does not
  pretend otherwise.** Back Market's price, storage, model and colour controls write a URL
  *fragment* — `#price=600&price=900`, `#storage=256%2520GB` — and its JavaScript
  re-renders from that after the page has loaded. A fragment is not part of an HTTP
  request, so it never reaches the server; and the query-string spelling of the same
  filters is silently ignored, measured side by side against the fragment form. So
  `minPrice`, `maxPrice` and `brands` are applied to the rows after fetching. They narrow
  what you receive, not what the site sends, and they do not extend the 1,000-item window.
  Two controls are absent entirely because they are not real: there is **no grade or
  condition facet on a category page at all** (the `backbox_grade_label__in` parameter
  matches no control the site offers), and there is no `min_price` parameter.
- **Filters do nothing on the `electronica` catch-all category.** That top-level
  pseudo-category supports pagination but not faceting: the same fragment controls that
  work immediately on a leaf category leave its count and cards unchanged. Prefer real
  leaf categories.
- **Grade, battery, storage, colour and SIM labels are the site's own Spanish wording,
  verbatim** — `Excelente`, `Estándar Salud al 85-99 %`, `SIM física + eSIM`. They are not
  mapped to an enum, because a partly-known vocabulary would quietly mislabel an option
  the site added later.
- **Opening every product page costs 20 to 50 times more per row** than browsing, because
  a category page carries 31 models per request and a product page carries one. Measured:
  about 0.05 USD per 1,000 rows for listing pages against roughly 0.88 to 1.50 USD extra
  per 1,000 rows with `fetchProductDetails` on. Nothing row-unique lives there — the price
  when new is already on the card, and the variant matrix is one shared table per model —
  which is why it is off by default.
- **A run that returns zero rows fails rather than succeeding empty**, so a silent
  misconfiguration cannot look like "no stock today". A genuinely empty category is
  recognised from the site's own `0 productos` count *inside* the result window, never
  from an absence of rows.
- **A blocked response is never read as an empty page.** backmarket.es answers an
  unrecognised client with a 34-byte error body rather than a page, so this Actor validates
  the shape of what it parsed and raises instead of pushing plausible-looking nothing.

### Privacy

**No personal data is collected, because there is none to collect.** Back Market sells
through merchants but does not name them anywhere on a category or product page: every
`data-test` hook matching `seller`, `merchant` or `vendor` is empty on both surfaces, and
the only seller wording on a product page is the generic notice that items may ship from
different sellers. No merchant name, no seller id, no natural-person identifier of any
kind appears in the output, and the Actor's tests assert that no row ever acquires one.

### How much does it cost to scrape Back Market?

Pricing is pay-per-event: **$0.001 per result** written to the dataset (**$1.00 per 1,000
results**), plus **$0.00005** charged once when the run starts. There is no subscription —
you pay only for what you get.

### FAQ

**Is a proxy required to scrape Back Market?** Yes. Apify Unblocker is the only transport
measured to reach this site at all — no proxy, datacenter and residential all failed.
There is no `proxyConfiguration` input to set: Unblocker is wired in internally so a run
can't be accidentally misconfigured onto a transport that doesn't work.

**Can I filter by price, storage or colour through the site's own controls?** Not over
HTTP. Back Market's filter controls write a URL *fragment* (`#price=`, `#storage=`), which
a browser applies client-side after the page loads and which never reaches the server. This
Actor applies `minPrice`, `maxPrice` and `brands` to the rows after fetching instead, so
they narrow what you receive rather than pretending to be server-side filters.

**Is merchant or seller identity included in the output?** No, for any row. Back Market
does not name individual merchants on its category or product pages at all — there is
nothing to withhold because nothing is published.

### More second-hand marketplace scrapers

| Actor | What it scrapes |
|---|---|
| [Wallapop Scraper](https://apify.com/reventadata/wallapop-scraper) | Wallapop, Spain — any keyword, phone mode with battery-health parsing |
| [Wallapop + Vinted + Milanuncios Scraper — Spain](https://apify.com/reventadata/wallapop-vinted-milanuncios-scraper) | Wallapop, Vinted and Milanuncios in Spain — one keyword, one merged table |
| [Cash Converters Spain Scraper](https://apify.com/reventadata/cashconverters-scraper) | Cash Converters Spain — graded second-hand stock with price when new |
| [Milanuncios Scraper](https://apify.com/reventadata/milanuncios-scraper) | Milanuncios — 17 categories, condition, storage and battery-health filters |
| [Gumtree UK Scraper](https://apify.com/reventadata/gumtree-scraper) | Gumtree UK — any category, vehicle and phone details parsed from the ad's own text |
| [OLX Romania Scraper](https://apify.com/reventadata/olx-ro-scraper) | OLX Romania (olx.ro) — any keyword, no proxy required |
| [OLX Brazil Scraper](https://apify.com/reventadata/olx-scraper) | OLX Brazil (olx.com.br) — any category, structured attributes from the site's own detail chips |
| [Vinted Spain Scraper](https://apify.com/reventadata/vinted-scraper) | Vinted Spain (vinted.es) — any keyword or category, no proxy required |
| [Todocolección Scraper](https://apify.com/reventadata/todocoleccion-scraper) | Todocolección — collectibles, antiques and auctions, fixed-price and bidding modelled apart |
| [eBay Spain Scraper](https://apify.com/reventadata/ebay-scraper) | eBay Spain (ebay.es) — active listings with seller type |

# Actor input Schema

## `categories` (type: `array`):

One or more Back Market Spain category pages, copied straight from the address bar — for example https://www.backmarket.es/es-es/l/iphone/aabc736a-cb66-4ac0-a3b7-0f449781ed39. The slug alone is not enough: a category is addressed by its slug AND the UUID that follows it. Free-text search is deliberately not offered, because the site's robots.txt reserves its search endpoint while leaving category browsing open.

## `minPrice` (type: `integer`):

Optional, in whole euros, compared against the card's starting-from price. Applied after fetching, not sent to the site: Back Market's own price filter is a URL fragment (#price=600\&price=900) which is never transmitted in an HTTP request, and its query-string spelling is silently ignored by the server. Omitted entirely when empty — never defaulted to 0.

## `maxPrice` (type: `integer`):

Optional, in whole euros, compared against the card's starting-from price. Applied after fetching, for the same reason as the minimum: the site's real price control is a URL fragment and cannot be sent by any HTTP client.

## `brands` (type: `array`):

Optional. Keep only rows whose brand matches one of these, using the site's own lowercase brand value from the card — for example apple, samsung, xiaomi. Matching is case-insensitive. Applied after fetching, like the price bounds. Leave empty for every brand in the category.

## `fetchProductDetails` (type: `boolean`):

Off by default. When on, each model's product page is fetched to add its full variant matrix — every grade, battery health, storage, colour and SIM option with its own price. It costs roughly 20 to 50 times more per row than browsing alone, about 0.88 to 1.50 USD extra per 1,000 rows against about 0.05 USD for the listing pages, because a category page carries 31 models per request and a product page carries one. Nothing row-unique lives there: the price when new is already on the listing card, and the variant matrix is one shared table per model rather than a list of individual sellers.

## `maxItems` (type: `integer`):

Hard cap on rows pushed across all categories.

## `maxPages` (type: `integer`):

Back Market serves at most 1,000 items per category — 32 pages of 31 models plus a partial page of 8 — no matter how large the category really is. Past that the site returns no cards AND reports '0 productos' even for a category that claimed 5,888 one page earlier, so this Actor stops at the window rather than fetching that lie. Clamped to 33.

## Actor input object example

```json
{
  "categories": [
    "https://www.backmarket.es/es-es/l/iphone/aabc736a-cb66-4ac0-a3b7-0f449781ed39"
  ],
  "fetchProductDetails": false,
  "maxItems": 300,
  "maxPages": 33
}
```

# Actor output Schema

## `matches` (type: `string`):

Matching models: starting price, price when new, rating, review count, available colours and a link to the product page.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "categories": [
        "https://www.backmarket.es/es-es/l/iphone/aabc736a-cb66-4ac0-a3b7-0f449781ed39"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("reventadata/backmarket-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "categories": ["https://www.backmarket.es/es-es/l/iphone/aabc736a-cb66-4ac0-a3b7-0f449781ed39"] }

# Run the Actor and wait for it to finish
run = client.actor("reventadata/backmarket-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "categories": [
    "https://www.backmarket.es/es-es/l/iphone/aabc736a-cb66-4ac0-a3b7-0f449781ed39"
  ]
}' |
apify call reventadata/backmarket-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reventadata/backmarket-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/G9SkanLUKnK8YIoVX/builds/nxxWmNa1xORhDlVOu/openapi.json
