# Target.com Product Scraper API (`dami_studio/target-scraper`) Actor

Scrape Target.com search and category listings plus product detail: TCIN, title, brand, price, promotions, ratings, shipping and pickup stock, images and URL.

- **URL**: https://apify.com/dami\_studio/target-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** E-commerce, Developer tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.78 / 1,000 product returneds

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

## Target.com Product Scraper

Pulls product data out of [Target](https://www.target.com), the US retailer, as structured rows.

Type a search term, paste a category link, or paste a single product link. You get one row per
product: TCIN, title, brand, the price Target is showing right now, whether it is marked down,
any promotion attached to it, star rating and review count, whether it ships, whether it is on the
shelf at a store you name, the image set, and the product URL.

No account, no API key, no cookies to paste. You put in words or links, you get rows.

### A real row

This came back from a live run, trimmed only where the image list repeats:

```json
{
  "tcin": "92084780",
  "title": "Gourmia Digital 3qt Air Fryer with Guided Cooking Prompts Black/Stainless Steel Finish",
  "brand": "Gourmia",
  "url": "https://www.target.com/p/digital-3qt-air-fryer-with-guided-cooking-prompts-black-stainless-steel-finish/-/A-92084780",
  "price": 39.99,
  "formattedPrice": "$39.99",
  "regularPrice": 39.99,
  "comparisonPrice": null,
  "onSale": false,
  "savingsAmount": null,
  "savingsPercent": null,
  "unitPrice": null,
  "unitPriceSuffix": null,
  "priceType": "reg",
  "priceNote": null,
  "pricedAtStoreId": "3991",
  "promotions": [],
  "rating": 4.18,
  "reviewCount": 471,
  "onlineAvailability": "IN_STOCK",
  "soldOut": false,
  "outOfStockInAllStores": false,
  "pickupAvailability": "IN_STOCK",
  "pickupStoreId": "1375",
  "pickupStoreName": "Mpls Nicollet Mall",
  "pickupQuantity": 6,
  "scheduledDeliveryAvailability": "IN_STOCK",
  "shipsBy": "2026-09-21",
  "primaryImage": "https://target.scene7.com/is/image/Target/GUEST_6ec22cbf-b531-4468-86ce-f0194548211b",
  "images": ["https://target.scene7.com/is/image/Target/GUEST_6ec22cbf-b531-4468-86ce-f0194548211b", "..."],
  "dpci": "072-04-6561",
  "upc": null,
  "itemType": "Electric Fryers",
  "departmentId": "72",
  "classId": "4",
  "categoryId": "ncrpx",
  "highlights": [
    "3-quart air fryer can cook for one or a few",
    "8 One-Touch Cooking Functions eliminate guesswork"
  ],
  "specifications": [
    "Dimensions (Overall): 10.47 Inches (H) x 11.46 Inches (W) x 8.98 Inches (D)",
    "Capacity (Volume): 3 Quart"
  ],
  "description": null,
  "ratingDistribution": null,
  "searchTerm": "air fryer",
  "position": 1,
  "scrapedAt": "2026-09-19T20:14:02.115Z"
}
```

A sale row looks like this instead. `onSale` flips, and the old price and the saving come with it:

```json
{
  "price": 2.69,
  "formattedPrice": "$2.69",
  "regularPrice": 2.69,
  "comparisonPrice": 2.99,
  "onSale": true,
  "savingsAmount": 0.3,
  "savingsPercent": 10,
  "unitPrice": "$0.36",
  "unitPriceSuffix": "/ounce",
  "promotions": [
    {
      "message": "Buy 1 get 1 50% off M&M's, Reese's & more candy",
      "promotionId": "941468854",
      "type": "BOGO",
      "thresholdType": "quantity",
      "thresholdValue": 1,
      "rewardType": "PercentageOff",
      "rewardValue": 50
    }
  ]
}
```

### Input

Three ways in, and you can mix them in one run.

```json
{
  "searchTerms": ["air fryer", "paper towels"],
  "maxItems": 200
}
```

```json
{
  "startUrls": [
    "https://www.target.com/c/air-fryers-kitchen-appliances/-/N-5xtg6",
    "https://www.target.com/b/gourmia/-/N-qhfwe",
    "https://www.target.com/p/-/A-92084780"
  ],
  "maxItems": 100
}
```

Add a postcode and the prices and the pickup stock change to a real shop instead of Target's
online default:

```json
{
  "searchTerms": ["milk", "bread"],
  "zipCode": "55403",
  "maxItems": 50
}
```

| Field | What it does |
|---|---|
| `searchTerms` | One search per line. Every product the search returns becomes a row. |
| `startUrls` | Target search, category, brand or product links. Product links always come back with the long description. |
| `maxItems` | Hard cap on rows across everything above. This is what bounds the bill. |
| `zipCode` | Five-digit US postcode. Prices and pickup stock come from the nearest store. |
| `includeAvailability` | On by default. Stock for every row. One extra request per 30 products. |
| `includeFullDetail` | Off by default. Long description and the star-by-star review split, at one extra request per product. |
| `proxyConfiguration` | Leave it alone unless you need a specific network. |

### Pickup stock needs a postcode

Without `zipCode` you get Target's online view: `onlineAvailability`, `soldOut`,
`scheduledDeliveryAvailability` and `shipsBy` are filled, and the three pickup fields come back
`null`. That is not a gap in the data, it is Target answering as a shopper who has not picked a
store.

Set `zipCode` and the run resolves the nearest shop once, then `pickupAvailability`,
`pickupStoreId`, `pickupStoreName` and `pickupQuantity` fill in, `pickupQuantity` being the
number of units that store says it has. Prices move too: `pricedAtStoreId` changes from `3991`,
Target's online store number, to the real shop's, and grocery prices in particular are not the
same in both.

### How many products you can get

Target stops any single search or category at **1,227 products**: 1,199 as a starting offset
plus a final page of 28. That is its limit, not the scraper's. If you need deeper coverage of a
big category, split it: search the sub-categories separately, or narrow the term.

`maxItems` goes up to 2,000 across a whole run, so several searches in one go can exceed 1,227
in total.

### Pricing

You are charged **per product row**. Nothing else bills.

- Sample rows and diagnostic rows are free. If you start a run with nothing filled in, you get one
  row explaining what to put where, and it costs nothing.
- A term Target stocks nothing for writes a free diagnostic row. See below, because this one is
  worth understanding.
- A product link that Target no longer recognises writes a free diagnostic row.
- A run that is refused and retried still costs nothing beyond the products it actually delivered.

`maxItems` is the ceiling on the bill, so set it to what you are willing to pay for.

### Searching for something Target does not sell

Target never answers a search with "no matches". Ask it for "uranium hexafluoride" and it returns
HTTP 200, a page of 28 products, and a total of 206, none of which have anything to do with the
question. Handing those over would bill you for noise, so the first page of every keyword search
is checked against the words you searched for, and if essentially none of the results mention any
of them you get a free diagnostic row instead of 28 charges.

The check was calibrated on 66 real shopping terms and 12 that Target stocks nothing for. None of
the 66 real terms was rejected, including awkward ones like "bed sheets", where the products are
all titled "Sheet Set", and "sous vide immersion circulator". It is deliberately loose in the
other direction: a term like "aircraft turbine blade" gets through, because Target genuinely sells
things with "blade" in the title and dropping a real result is worse than passing on a weak one.

Category and brand links skip this check entirely, since there is no query to compare against.

### What this does not do

Worth reading before you buy.

- **Review text.** You get the star average, the review count, and with `includeFullDetail` the
  count at each star. The written reviews themselves are a different data set and are not here.
- **Stock at more than one store.** Pickup stock is for the single nearest store to the postcode
  you gave. Asking "which of these 40 shops has it" is a different job.
- **Variants as separate rows.** A shirt in six colours is one row, the one Target shows in the
  listing. Sizes and colours are not expanded.
- **Anything behind a sign-in.** Circle member pricing, your order history, cart behaviour. None
  of that is reachable and none of it is attempted.
- **Non-US Target.** This is target.com. Target Australia is a different company entirely.
- **`upc` on listing rows.** The barcode only exists on the product's own page data, so it is
  filled when `includeFullDetail` is on or when you pasted a product link, and `null` otherwise.
  Same for `description` and `ratingDistribution`.
- **Sponsored placements.** Paid ad slots inside Target's search results are skipped. You get the
  catalogue, not the ads. If you specifically want to study ad placements, this is the wrong tool.

### Things that will trip you up

**Target's prices move.** The price in a row is what target.com was showing at `scrapedAt`, for
the store in `pricedAtStoreId`. Two runs an hour apart can honestly disagree.

**`regularPrice` is not always the "before" price.** When something is marked down, Target puts
the old price in `comparisonPrice` and leaves `regularPrice` equal to the current one. Read
`onSale` first, then `comparisonPrice`.

**A category link is a node id.** `https://www.target.com/c/anything/-/N-5xtg6`: the `5xtg6` at
the end is what matters and the words before it are decoration. Brand pages use the same shape,
so `/b/gourmia/-/N-qhfwe` works too.

**Searching a very broad word is slower than it looks.** "shoes" has tens of thousands of matches
and pages 28 at a time, so a 1,000-row run is 36 requests. `maxItems` is your friend.

### FAQ

**Does this need a Target account or an API key?**
No. Nothing is logged in, nothing is stored, and there is no key to obtain.

**Can I scrape a whole category?**
Up to 1,227 products of it, which is Target's own ceiling. Beyond that, split the category.

**Can I get prices for my local store?**
Yes. Set `zipCode`. The run finds the nearest shop and prices against it. Grocery and seasonal
items are where you see the difference most.

**How do I monitor a fixed list of products?**
Put their product links in `startUrls` and schedule the run. Each link returns one row with the
full description, price, promotion and stock. TCINs are stable, so the rows line up run to run.

**What is a TCIN?**
Target's own product number: the digits after `A-` in a product URL. It is the id you want as a
join key. `dpci` is Target's internal shelf number and is useful in stores; `upc` is the
manufacturer's barcode, when Target publishes one.

**Why is `pickupQuantity` sometimes null while `pickupAvailability` says IN\_STOCK?**
Target does not publish a unit count for every item. The status is the reliable field; the count
is a bonus when it is there.

**Does it get blocked?**
It handles refusals itself: a refused request is retried on a different route, and if a search
genuinely cannot be completed you get a free diagnostic row saying so rather than a silent empty
result. Measured on 24 varied searches on 2026-09-19, every one returned products.

**What happens if I give it a bad URL?**
A free diagnostic row naming the URL and showing the three shapes that do work. The run carries
on with everything else and exits normally.

**I searched something and got a diagnostic row instead of products. Why?**
Target had nothing matching and answered with unrelated items, so they were left out rather than
billed to you. The row carries the term and a `relevance` score. If you think it was wrong, a
category or brand link fetches the same products with no check applied.

### Output fields

| Field | Notes |
|---|---|
| `tcin` | Target's product number. The join key. |
| `title`, `brand`, `url` | `url` is Target's own canonical link for the product. |
| `price`, `formattedPrice`, `regularPrice`, `comparisonPrice` | `comparisonPrice` is the "was" price on a markdown. |
| `onSale`, `savingsAmount`, `savingsPercent` | Filled when Target is showing a was/now price. |
| `unitPrice`, `unitPriceSuffix` | Grocery and household items, e.g. `$0.36` `/ounce`. |
| `priceType`, `priceNote`, `pricedAtStoreId` | Which store the price is for. `3991` means online. |
| `promotions[]` | `message`, `promotionId`, `type`, `thresholdType`, `thresholdValue`, `rewardType`, `rewardValue`. |
| `rating`, `reviewCount` | Star average and how many reviews it is from. |
| `ratingDistribution` | Counts per star. Needs `includeFullDetail` or a pasted product link. |
| `onlineAvailability`, `soldOut`, `outOfStockInAllStores` | Shipping view. |
| `pickupAvailability`, `pickupStoreId`, `pickupStoreName`, `pickupQuantity` | Needs `zipCode`. |
| `scheduledDeliveryAvailability`, `shipsBy` | Same-day delivery status and the latest ship date. |
| `primaryImage`, `images[]` | Full-resolution image URLs. |
| `dpci`, `upc` | Shelf number and barcode. `upc` needs full detail. |
| `itemType`, `departmentId`, `classId`, `categoryId` | Target's own classification. |
| `highlights[]`, `specifications[]` | The bullets from the product page, with the markup stripped. |
| `description` | The long copy. Needs full detail. |
| `searchTerm`, `position`, `scrapedAt` | Which search produced the row, where it ranked, and when. |

Rows that are not products carry `_sample: true` or `_diagnostic: true` and `charged: false`, so
you can filter them out with one check.

# Actor input Schema

## `searchTerms` (type: `array`):

What to look up on target.com, one per line — "air fryer", "lego", "paper towels". Each term is searched separately and every product it returns becomes a row.

## `startUrls` (type: `array`):

Paste Target pages instead of typing terms. Search pages (https://www.target.com/s?searchTerm=air+fryer), category and brand pages (…/-/N-5xtg6) and single product pages (…/-/A-92084780) all work, mixed together. Product links always come back with the full description.

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

Hard cap on products returned across everything above. You pay per product, so this is what bounds the bill. Target stops any one search or category at 1,227 products.

## `zipCode` (type: `string`):

A five-digit US postcode, for example 55403. Sets prices and pickup stock to the nearest Target store. Leave it empty to get the prices target.com shows an online shopper who has not chosen a store.

## `includeAvailability` (type: `boolean`):

Look up whether each product ships, is sold out, and — when a postcode is set — whether it is on the shelf for pickup. Costs one extra request per 30 products.

## `includeFullDetail` (type: `boolean`):

Fetch each product's own page data for the long description and the star-by-star review breakdown. This is one extra request per product, so a 500-product run takes noticeably longer. Product links pasted into "Target URLs" always get this regardless.

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

Optional. Leave this alone unless you need the run to go out through a particular network. Your own proxy servers are used exactly as given.

## Actor input object example

```json
{
  "searchTerms": [
    "air fryer"
  ],
  "startUrls": [],
  "maxItems": 50,
  "includeAvailability": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One dataset row per Target product: TCIN, title, brand, URL, current and regular price, sale flags and unit price, promotions, star rating and review count, shipping and pickup stock, images, DPCI, UPC, item type and category ids, and the on-page highlight and specification bullets. Empty input, an unrecognised URL or a search that returns nothing writes an uncharged sample or diagnostic row instead.

# 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 = {
    "searchTerms": [
        "air fryer"
    ],
    "startUrls": [],
    "maxItems": 50,
    "zipCode": "",
    "includeAvailability": true,
    "includeFullDetail": false,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/target-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 = {
    "searchTerms": ["air fryer"],
    "startUrls": [],
    "maxItems": 50,
    "zipCode": "",
    "includeAvailability": True,
    "includeFullDetail": False,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/target-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 '{
  "searchTerms": [
    "air fryer"
  ],
  "startUrls": [],
  "maxItems": 50,
  "zipCode": "",
  "includeAvailability": true,
  "includeFullDetail": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call dami_studio/target-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/target-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/SqwCZ7MQmk9vThiwK/builds/NXdynlQSULiekVkrj/openapi.json
