# Mattress Firm Scraper - Mattress Price & Stock by Size (USA) (`superslowsloth/mattressfirm-products`) Actor

Mattress Firm scraper: product pages or the whole sitemap to one row per size offer (Twin to Cal King) with USD price, in-stock flag, SKU, barcode, brand, firmness, rating and reviews. For mattress price and stock watching.

- **URL**: https://apify.com/superslowsloth/mattressfirm-products.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** E-commerce, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.45 / 1,000 product scrapeds

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Mattress Firm scraper: mattress price and stock by size (USA)

This Mattress Firm scraper reads product pages on [Mattress Firm](https://www.mattressfirm.com)
(the US mattress retailer: mattresses, adjustable bases, foundations, pillows, protectors,
headboards and bedding) and returns **one row per size**: the **USD price of the Twin, the
Twin XL, the Full, the Queen, the King and the Cal King** of each product, an in-stock flag,
the SKU and barcode, brand, firmness, rating and review count, the link and the photo. Give it
the products you track, or switch on the sitemap to read the whole site, and run it on a
schedule: diff the dataset to get a **mattress price and stock watch**, with a price per size
and not a single "from" price per product.

No account, no API key, no login, no browser. One request returns every size of a product.

### Input

| Field | Notes |
|---|---|
| `productUrls` | Product pages to read, one per line: the page address (`https://www.mattressfirm.com/tempur-breeze-pro-pillow/5637524826.p`), the path, or just the product number (`5637524826`). A category address (ending in `.c`) is refused with a message, because Mattress Firm draws its category pages in the browser: use `fromSitemap` to discover products. The same product given twice, or with different `?variantid=` values, is read once. |
| `fromSitemap` | Also read every product in Mattress Firm's sitemap (`/sitemap_index.xml`: about 170 products when this actor was built), after the ones in `productUrls`. Default off. About a third of the sitemap's products are no longer sold; they are skipped at no charge (see below). |
| `maxItems` | Stop after writing this many rows (default 100, up to 50,000). A row is one size, so a mattress sold in six sizes is six rows. Products are read in the order given, the sitemap's after yours. |
| `proxyConfiguration` | Apify RESIDENTIAL proxy by default. Mattress Firm sits behind Cloudflare, which refuses datacenter addresses. |

Example input:

```json
{
  "productUrls": [
    "https://www.mattressfirm.com/frisco-21-medium-euro-pillowtop-mattress/5637674080.p",
    "https://www.mattressfirm.com/tempur-breeze-pro-pillow/5637524826.p"
  ],
  "maxItems": 30
}
```

### Output

Prices are numbers in US dollars; `currency` is `USD` on every offer measured. **One row is one
product size offer**: a mattress is a row per size, a pillow a row per size and height, a headboard
a row per size and colour, and a product sold in one version (a comforter set, a sleep mask) is one
row with no size. `sku` is the unique key of a row and `product_id` ties the rows of one product
together. A row reports the price on the day it was read (`scraped_at`); keep the datasets of
scheduled runs and compare them to see a price move or a size go out of stock.

| Field | Notes |
|---|---|
| `product_id`, `sku` | Mattress Firm's product id (the number before `.p` in the address) and the variant id of the row, both strings. `sku` is unique per row. A product with a single version, asked for without `?variantid=`, prints an empty SKU on the page: the row then carries the product id as its `sku`. |
| `name`, `product_name` | `name` spells the variant out ("Frisco 2.1 Medium Euro Pillowtop Mattress - Queen"); `product_name` is the product alone, and equals `name` for a single-version product. |
| `brand` | As the page names it (`Sealy`, `Tempur-Pedic`, `Sleepy's`); null if absent. |
| `size` | The size as the page names it: `Twin`, `Twin XL`, `Full`, `Queen`, `King`, `Cal King`, `Split Cal King`, `Full/Queen` and other spellings the page uses. **Null for a product with a single version.** |
| `color`, `height`, `foundation` | The other axes a product varies by: a headboard's colour, a pillow's height (`Low`, `Medium`, `High`), a box spring's foundation height. Null where the product does not vary by it. |
| `firmness` | The comfort level the page gives the variant (`MEDIUM COMFORT`, `FIRM COMFORT`), on the products that carry one; null otherwise. |
| `currency`, `price` | `USD`, and the price of this size today. The page's structured data carries **no was-price or list price**, so there is no column for one: a markdown shows up as the price changing between two runs. |
| `in_stock`, `availability` | `availability` is the page's schema.org value as sent (`InStock`, `OutOfStock`); `in_stock` is true for `InStock`, false for `OutOfStock`, and null for anything else or nothing. It is per size. |
| `condition` | `new`, `used` or `refurbished`; null when the offer says none. |
| `gtin` | The variant's barcode as sent (14 digits, zero-padded); null when absent. |
| `url`, `image_url` | The variant's page address (with its `?variantid=`) and its first photo. |
| `rating`, `review_count` | The product's average star rating (rounded to two decimals) and the number of reviews it is from, shared by every size of the product. **Both null where the page carries no rating** (the single-version products measured had none). |
| `scraped_at` | When the row was read, UTC, ISO 8601. |

Example row (a real row, 2026-11-02):

```json
{
  "product_id": "5637674080",
  "sku": "5637674850",
  "name": "Frisco™ 2.1 Medium Euro Pillowtop Mattress - Queen",
  "product_name": "Frisco™ 2.1 Medium Euro Pillowtop Mattress",
  "brand": "Sealy",
  "size": "Queen",
  "color": null,
  "height": null,
  "foundation": null,
  "firmness": "MEDIUM COMFORT",
  "currency": "USD",
  "price": 649.99,
  "in_stock": true,
  "availability": "InStock",
  "condition": "new",
  "gtin": "00748369787681",
  "url": "https://www.mattressfirm.com/frisco-21-medium-euro-pillowtop-mattress/5637674080.p?variantid=5637674850",
  "image_url": "https://images-us-prod.cms.dynamics365commerce.ms/cms/api/cncgmclkfv/imageFileData/search?fileName=/Products/144589P%20%5E%20%20%5E%20Queen%20%5E%20%20%5E%20Prime_000_001.png&fallback%3D%2FProducts%2F144589P_000_001.png",
  "rating": 4.35,
  "review_count": 1992,
  "scraped_at": "2026-11-02T09:15:42Z"
}
```

#### Honest nulls

A field the page did not carry is `null`, never `0` or `""`.

- **No was-price, discount or sale flag.** Mattress Firm's page data holds one price per size and
  no list price. A sale is a lower `price` than on the previous run.
- **No description, specs or review text.** This is the price-and-stock table, one row per size.
- **No store inventory.** `in_stock` is the online offer, not a store's shelf.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is validated, so a run that
  fails on bad input costs nothing.
- **$0.00245** per product written (`product-scraped`), i.e. $2.45 per 1,000 rows, 30% under the
  cheapest comparable Mattress Firm actor on the Apify Store ($0.0035 per item) when this one was
  built. One row is one product size offer, so a mattress sold in six sizes is six rows. A row is
  charged only after it is written. A product Mattress Firm no longer sells, a product that cannot
  be read, and a size the same product repeats are not written and not charged.

### How it fetches

It reads the schema.org structured data (`application/ld+json`) that every Mattress Firm product
page carries for search engines: a product group with one product and one offer per variant. The
page is about 0.2 to 0.8 MB. Measured 2026-11-01 from the Apify platform on a probe run of 10
requests per proxy group: **RESIDENTIAL answered a real page 6 of 10 times and datacenter 0 of 10**
(every refusal was a Cloudflare page, HTTP 403 "Attention Required!" or "Just a moment..."). The
actor therefore defaults to the residential group and treats a refusal (403, 429, 5xx), a 200 that
carries no product data, or a dropped connection as the address being blocked: it retries from a
fresh exit address **and a different browser fingerprint** (seven are tried in turn, one attempt
each). Product pages are read four at a time and written in the order you gave them.

Things worth knowing, all measured on 2026-11-02:

- **A third of the sitemap is dead.** The sitemap lists 222 addresses of 174 distinct products,
  all dated 2025-07-08. Read in full, 113 had offers, 60 were gone and 1 was refused by a rate limit
  on the machine used for the test: Mattress Firm answers a product it no longer sells with HTTP 200
  and its 404 page (33) or with a category page (27). The actor recognises both, reports the product
  as skipped in the run log, does not retry it and does not charge for it.
- **One product, one request.** A product page lists every size whatever `?variantid=` it is asked
  with, so the sitemap's several addresses of one product are read once.
- **Single-version products.** A product with one offer (a comforter set) has no size, no rating and
  no product group; its row has `size` null.
- **Category pages are not read.** They are drawn in the browser and hold no product data in the
  HTML.
- **A page with no usable price** (the offers carry none) fails that product with a message and is
  not charged, rather than writing a zero.

If Mattress Firm changes its page layout, the run fails with a message instead of returning empty rows.

# Actor input Schema

## `productUrls` (type: `array`):

Mattress Firm product pages to read, one per line. Give the product page address (https://www.mattressfirm.com/tempur-breeze-pro-pillow/5637524826.p), the path, or just the product number (5637524826). Each product gives one row per size it is sold in. A category address (ending in .c) is not accepted, because Mattress Firm draws its category pages in the browser: use the sitemap option below to find products. A product Mattress Firm no longer sells is skipped at no charge. The same product given twice, or with different ?variantid= values, is read once.

## `fromSitemap` (type: `boolean`):

Read the product addresses listed in Mattress Firm's sitemap (https://www.mattressfirm.com/sitemap_index.xml, about 170 products when this actor was built) after the addresses above, and return a row for every size. About a quarter of the sitemap's products are no longer sold and are skipped at no charge. Use maxItems to stop early.

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

Stop after writing this many rows (one row is one product size, so a mattress sold in six sizes is six rows). Products are read in the order given, the sitemap's after yours. Up to 50,000 are accepted.

## `proxyConfiguration` (type: `object`):

Use Apify's RESIDENTIAL proxy group (the default). mattressfirm.com sits behind Cloudflare, which refused every Apify datacenter request and about 4 in 10 residential ones with HTTP 403 when this actor was built. A refused request is retried from a fresh address and a different browser fingerprint.

## Actor input object example

```json
{
  "productUrls": [
    "https://www.mattressfirm.com/tempur-breeze-pro-pillow/5637524826.p",
    "https://www.mattressfirm.com/frisco-21-medium-euro-pillowtop-mattress/5637674080.p",
    "https://www.mattressfirm.com/br800-135-medium-pillow-top-mattress/5637145714.p",
    "https://www.mattressfirm.com/basic-adjustable-base/5637401826.p",
    "https://www.mattressfirm.com/posturepedic-plus-norman-ii-15-medium-euro-top-mattress/5637547442.p"
  ],
  "fromSitemap": false,
  "maxItems": 30,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `items` (type: `string`):

No description

# 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 = {
    "productUrls": [
        "https://www.mattressfirm.com/tempur-breeze-pro-pillow/5637524826.p",
        "https://www.mattressfirm.com/frisco-21-medium-euro-pillowtop-mattress/5637674080.p",
        "https://www.mattressfirm.com/br800-135-medium-pillow-top-mattress/5637145714.p",
        "https://www.mattressfirm.com/basic-adjustable-base/5637401826.p",
        "https://www.mattressfirm.com/posturepedic-plus-norman-ii-15-medium-euro-top-mattress/5637547442.p"
    ],
    "maxItems": 30,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/mattressfirm-products").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 = {
    "productUrls": [
        "https://www.mattressfirm.com/tempur-breeze-pro-pillow/5637524826.p",
        "https://www.mattressfirm.com/frisco-21-medium-euro-pillowtop-mattress/5637674080.p",
        "https://www.mattressfirm.com/br800-135-medium-pillow-top-mattress/5637145714.p",
        "https://www.mattressfirm.com/basic-adjustable-base/5637401826.p",
        "https://www.mattressfirm.com/posturepedic-plus-norman-ii-15-medium-euro-top-mattress/5637547442.p",
    ],
    "maxItems": 30,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/mattressfirm-products").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 '{
  "productUrls": [
    "https://www.mattressfirm.com/tempur-breeze-pro-pillow/5637524826.p",
    "https://www.mattressfirm.com/frisco-21-medium-euro-pillowtop-mattress/5637674080.p",
    "https://www.mattressfirm.com/br800-135-medium-pillow-top-mattress/5637145714.p",
    "https://www.mattressfirm.com/basic-adjustable-base/5637401826.p",
    "https://www.mattressfirm.com/posturepedic-plus-norman-ii-15-medium-euro-top-mattress/5637547442.p"
  ],
  "maxItems": 30,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call superslowsloth/mattressfirm-products --silent --output-dataset

```

## MCP server setup

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

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/Q8v7H5wmyRnwf0rAm/builds/SSprLf3aYVsVyy5sQ/openapi.json
