# Product Image URLs Matcher (`automation-lab/sku-main-image-url-matcher`) Actor

Match requested SKUs against public JSON or CSV catalogs and return one validated primary product image URL per match.

- **URL**: https://apify.com/automation-lab/sku-main-image-url-matcher.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 90.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 item extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Product Image URLs Matcher

Match requested SKUs against public JSON or CSV product catalogs and return one validated primary product image URL per match.

This Actor turns a catalog join that is often done manually in spreadsheets into a repeatable product image URLs workflow. It fetches public feeds, finds requested SKUs, selects the first primary image field, follows safe redirects, verifies the final response is an image, and produces one spreadsheet-ready row for every unique requested SKU.

It does **not** download image files or crawl protected storefront pages.

### What this Actor does

1. Downloads up to 10 public JSON or CSV product catalogs.
2. Parses common catalog shapes automatically or follows your field mappings.
3. Matches as many as 10,000 requested SKUs.
4. Selects one main image URL from each matched record.
5. Validates HTTP status, redirects, and image content type.
6. Returns matched, missing, and invalid-image rows in the default dataset.

The first usable catalog match wins. Later catalog URLs act as ordered fallbacks for SKUs absent from earlier feeds.

### Who is it for?

- Ecommerce operators preparing marketplace imports
- Merchandising teams auditing product imagery
- Catalog migration teams joining supplier feeds to internal SKUs
- Agencies delivering clean SKU-to-image spreadsheets
- Data engineers enriching PIM, ERP, or warehouse records
- QA teams checking whether primary product image links still resolve

Use [Website Image Extractor](https://apify.com/automation-lab/website-image-extractor) when you need to discover every image referenced by HTML pages. Use [Web Image Downloader](https://apify.com/automation-lab/web-image-downloader) when you need the image files themselves rather than URL records.

### Why use Product Image URLs Matcher

A plain spreadsheet lookup does not tell you whether an image URL redirects, returns HTML, or is broken. A general web crawler may return logos, thumbnails, gallery images, and secondary views.

This Actor focuses on a narrower result:

- exact SKU join semantics;
- one selected image per match;
- explicit main-image field control;
- no image downloads;
- validation evidence in every matched row;
- diagnostics for missing and invalid records;
- JSON and CSV support in one workflow.

### Supported catalog formats

#### JSON

The Actor accepts a top-level array, one object, or an object containing a conventional array named:

- `products`
- `items`
- `records`
- `results`
- `data`

For other shapes, set `recordsPath` to a dot path such as `payload.catalog.products`.

#### CSV

CSV parsing supports quoted fields, commas inside quoted values, UTF-8 byte-order marks, blank lines, and uneven optional columns. The first row must contain column names.

When an image cell contains comma-separated URLs, the first URL is selected as the primary image.

### Automatic field detection

If mappings are omitted, the Actor checks common fields.

| Purpose | Common fields |
| --- | --- |
| SKU | `sku`, `SKU`, `variantSku`, `variant_sku`, `Variant SKU`, `id` |
| Primary image | `mainImageUrl`, `main_image_url`, `imageUrl`, `image_url`, `image`, `Images`, `images`, `thumbnail` |
| Product name | `name`, `title`, `Name`, `Title` |
| Product page | `productUrl`, `product_url`, `url`, `permalink`, `URL` |

Explicit fields may be dot paths. For example, use `variants.primary.sku` or `media.primary.url`.

### Getting started

1. Open the Actor input page.
2. Add one or more anonymously reachable catalog URLs.
3. Paste the SKUs you want to match.
4. Leave format and fields on automatic detection for a common feed.
5. Add explicit field paths for a custom schema.
6. Keep **Validate image URLs** enabled for import-ready evidence.
7. Run the Actor.
8. Export the default dataset as CSV, Excel, JSON, or another supported format.

A small working JSON input:

```json
{
  "catalogUrls": [
    { "url": "https://dummyjson.com/products?limit=100" }
  ],
  "skus": [
    "BEA-ESS-ESS-001",
    "GRO-BRD-CUC-021",
    "KIT-BRD-BOX-051"
  ],
  "validateImages": true
}
```

### Input parameters

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `catalogUrls` | array | required | One to 10 public JSON or CSV URLs |
| `skus` | string array | required | One to 10,000 requested SKUs |
| `catalogFormat` | string | `auto` | `auto`, `json`, or `csv` |
| `recordsPath` | string | empty | Dot path to a JSON record array |
| `skuField` | string | automatic | SKU field name or dot path |
| `imageField` | string | automatic | Primary image field name or dot path |
| `productNameField` | string | automatic | Optional name field or dot path |
| `productUrlField` | string | automatic | Optional product page field or dot path |
| `caseSensitive` | boolean | `false` | Require exact SKU capitalization |
| `validateImages` | boolean | `true` | Require a successful `image/*` response |
| `validationConcurrency` | integer | `10` | Parallel validators, from 1 to 25 |

Duplicate requested SKUs are emitted once. Leading and trailing whitespace is removed before matching.

### WooCommerce CSV example

The official WooCommerce sample export uses `SKU`, `Name`, and `Images` columns:

```json
{
  "catalogUrls": [
    {
      "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/plugins/woocommerce/sample-data/sample_products.csv"
    }
  ],
  "skus": [
    "woo-vneck-tee",
    "woo-hoodie",
    "woo-cap"
  ],
  "catalogFormat": "csv",
  "skuField": "SKU",
  "imageField": "Images",
  "productNameField": "Name",
  "validateImages": true
}
```

The Actor uses the first URL in `Images`, so secondary gallery views are not returned as separate matches.

### Multi-catalog fallback workflow

Supply feeds in priority order when different vendors or platforms own different SKUs:

```json
{
  "catalogUrls": [
    { "url": "https://dummyjson.com/products?limit=100" },
    {
      "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/plugins/woocommerce/sample-data/sample_products.csv"
    }
  ],
  "skus": [
    "BEA-ESS-ESS-001",
    "KIT-BRD-BOX-051",
    "woo-vneck-tee",
    "woo-hoodie",
    "missing-example-sku"
  ],
  "validateImages": true,
  "validationConcurrency": 5
}
```

This is useful for supplier fallback, catalog migrations, and recurring image-link QA.

### Output fields

| Field | Meaning |
| --- | --- |
| `sku` | Requested normalized SKU |
| `matchedSku` | Exact value found in the catalog |
| `status` | `matched`, `not_found`, or `invalid_image` |
| `imageUrl` | Primary URL selected from the source record |
| `resolvedImageUrl` | Final URL after validated redirects |
| `imageContentType` | Response media type, such as `image/webp` |
| `imageHttpStatus` | Final validation status code |
| `productName` | Optional catalog product name |
| `productUrl` | Optional absolute product page URL |
| `catalogUrl` | Final catalog URL that supplied the match |
| `catalogFormat` | `json` or `csv` |
| `validated` | Whether this run confirmed a successful image response |
| `error` | Concise missing or validation diagnostic |
| `matchedAt` | ISO 8601 processing timestamp |

### Example output

A successful current-format record looks like this:

```json
{
  "sku": "BEA-ESS-ESS-001",
  "matchedSku": "BEA-ESS-ESS-001",
  "status": "matched",
  "imageUrl": "https://cdn.dummyjson.com/product-images/beauty/essence-mascara-lash-princess/1.webp",
  "resolvedImageUrl": "https://cdn.dummyjson.com/product-images/beauty/essence-mascara-lash-princess/1.webp",
  "imageContentType": "image/webp",
  "imageHttpStatus": 200,
  "productName": "Essence Mascara Lash Princess",
  "productUrl": null,
  "catalogUrl": "https://dummyjson.com/products?limit=100",
  "catalogFormat": "json",
  "validated": true,
  "error": null,
  "matchedAt": "2026-01-15T12:00:00.000Z"
}
```

A missing SKU still gets a dataset row with `status: "not_found"`, null image fields, and a diagnostic. This makes spreadsheet reconciliation straightforward.

### Image validation behavior

Validation first sends an HTTP `HEAD` request. If a server rejects `HEAD` with 403 or 405, the Actor sends a ranged `GET` and cancels the body after headers arrive.

A validated match must have:

- a final 2xx status;
- an `image/*` content type;
- no private or local redirect target;
- no more than five redirects.

The Actor does not store the image file.

Set `validateImages` to `false` only when you want fast catalog joining and accept unverified URLs. Such matched rows have `validated: false` and no observed HTTP metadata.

### How much does it cost to match SKUs to product image URLs?

The Actor uses pay-per-event pricing:

- one small `start` event per run;
- one `item` event for each SKU that produces a usable primary image URL;
- no item event for `not_found` or `invalid_image` rows.

At the current BRONZE rate, a run costs **$0.005 to start plus $0.0008096 per validated match**. For example:

| Validated matches | Estimated BRONZE price |
| ---: | ---: |
| 10 | 0.0131 USD |
| 100 | 0.0860 USD |
| 700 | 0.5717 USD |

Your active Apify plan tier determines the exact per-item rate shown in Console. Final live rates always appear on the Actor pricing tab before you start a run.

### Reliability and retry behavior

Catalog requests retry only transient failures such as timeouts, 429 responses, and temporary 5xx responses. Stable invalid URLs and authorization failures are not retried blindly.

One failed catalog does not discard successful catalogs. The run fails only when no supplied catalog can be fetched and parsed.

Image-level failures produce `invalid_image` rows instead of failing the entire batch.

### Limits and important behavior

- Catalog URLs must be anonymously reachable over HTTP or HTTPS.
- Private, loopback, local-network, credential-bearing, and reserved-address URLs are rejected.
- Each catalog response is limited to 10 MB.
- Up to 10 catalogs and 10,000 SKUs are supported per run.
- SKU matching is case-insensitive by default.
- The first catalog candidate wins for duplicate SKUs.
- The first image in an array or comma-separated field is selected.
- The Actor does not discover product pages from a storefront domain.
- The Actor does not solve logins, CAPTCHAs, or signed private feeds.
- XML and spreadsheet files such as XLSX are not catalog input formats.

Convert an XLSX file to CSV or publish it as an anonymously reachable CSV endpoint before running.

### Spreadsheet and data-pipeline integrations

Export the dataset directly from Apify Console as CSV or Excel for a clean SKU-to-URL sheet.

For recurring work, connect the dataset to:

- Google Sheets
- Airtable
- Make
- Zapier
- Keboola
- webhooks
- your PIM or warehouse loader

A common pipeline is:

1. Export SKUs from a PIM.
2. Publish or expose the supplier catalog as JSON/CSV.
3. Run this Actor on a schedule.
4. Filter `status = matched` for imports.
5. Route `not_found` and `invalid_image` rows to a QA queue.
6. Compare `resolvedImageUrl` values with the previous run.

### Run with the Apify API using cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~sku-main-image-url-matcher/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "catalogUrls": [{"url":"https://dummyjson.com/products?limit=100"}],
    "skus": ["BEA-ESS-ESS-001", "GRO-BRD-CUC-021"],
    "validateImages": true
  }'
```

Use the returned run's `defaultDatasetId` to download results.

### Run with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('automation-lab/sku-main-image-url-matcher').call({
  catalogUrls: [{ url: 'https://dummyjson.com/products?limit=100' }],
  skus: ['BEA-ESS-ESS-001', 'GRO-BRD-CUC-021'],
  validateImages: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automation-lab/sku-main-image-url-matcher").call(
    run_input={
        "catalogUrls": [
            {"url": "https://dummyjson.com/products?limit=100"}
        ],
        "skus": ["BEA-ESS-ESS-001", "GRO-BRD-CUC-021"],
        "validateImages": True,
    }
)

items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use with Apify MCP

#### Claude Code setup

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/sku-main-image-url-matcher"
```

#### Claude Desktop setup

Use this JSON configuration in Claude Desktop:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/sku-main-image-url-matcher"
    }
  }
}
```

#### Cursor setup

Use the same MCP server URL in Cursor's MCP settings: `https://mcp.apify.com?tools=automation-lab/sku-main-image-url-matcher`.

#### VS Code setup

Add the same HTTP MCP server URL to your VS Code MCP configuration and name the server `apify`.

#### Example prompts

- “Match these SKUs against this public JSON feed and return only validated image rows.”
- “Check the WooCommerce CSV for these product SKUs and list broken primary images.”
- “Join this SKU batch across both supplier catalogs and summarize missing products.”

### Legality and responsible use

Only process catalogs you are authorized to access. Respect feed licenses, website terms, robots guidance where applicable, rate limits, intellectual-property rights, and privacy laws.

Do not use this Actor to bypass authentication or access controls. Public reachability does not automatically grant permission to redistribute catalog data or product images.

The Actor returns URLs and metadata; it does not transfer ownership or usage rights for image content.

### Troubleshooting

#### None of the supplied catalogs could be fetched

Check that every URL is public, anonymously reachable, and returns JSON or CSV rather than an HTML login page. Confirm that the response is under 10 MB.

#### Every SKU is `not_found`

Inspect capitalization and whitespace, then set `skuField` explicitly. For nested JSON, supply a dot path. Set `caseSensitive` to `true` only if capitalization differentiates real SKUs.

#### The JSON feed parsed only one record

Set `recordsPath` to the array, for example `payload.products`.

#### A matched SKU has `invalid_image`

Read `imageHttpStatus`, `imageContentType`, and `error`. The URL may be broken, redirect to HTML, require authentication, or reject automated validation.

#### The wrong image was selected

Set `imageField` to the catalog's canonical primary-image property. Arrays and comma-separated CSV image lists intentionally select their first entry.

#### Validation is slow

Increase `validationConcurrency` gradually up to 25. Keep it lower for rate-limited image hosts.

### FAQ

#### Does the Actor download images?

No. It validates headers and cancels fallback response bodies without storing image files.

#### Does it scrape ecommerce websites by SKU?

No. It joins SKUs against public JSON or CSV catalogs supplied by the user. It does not search arbitrary storefront pages.

#### Can I provide multiple catalogs?

Yes. Provide up to 10 URLs in priority order. Later feeds fill still-unmatched SKUs.

#### Are missing SKUs charged as items?

No. They are returned for reconciliation but do not emit the per-match item event.

#### Can it handle nested fields?

Yes. Use dot paths for `recordsPath`, `skuField`, `imageField`, `productNameField`, and `productUrlField`.

#### Can it return all gallery images?

No. The product contract is exactly one primary URL per matched SKU. Use Website Image Extractor for a broader inventory.

#### Can it validate redirects?

Yes. It follows up to five public HTTP redirects and records the final URL.

#### Can I schedule recurring checks?

Yes. Schedule the same input and compare each run's `resolvedImageUrl`, `status`, and validation fields in your downstream system.

### Related Automation Lab Actors

- [Website Image Extractor](https://apify.com/automation-lab/website-image-extractor) — discover image references, alt text, and page provenance from HTML.
- [Web Image Downloader](https://apify.com/automation-lab/web-image-downloader) — download supplied image URLs and return file metadata.
- [Structured Data Extractor](https://apify.com/automation-lab/structured-data-extractor) — extract JSON-LD and other structured data from public pages.

These Actors solve adjacent tasks; they do not replace the SKU-to-catalog matching workflow described here.

# Actor input Schema

## `catalogUrls` (type: `array`):

One to ten public JSON or CSV catalog URLs. Catalog responses are limited to 10 MB each.

## `skus` (type: `array`):

Requested SKU values. Duplicate values are matched once. Up to 10,000 SKUs are supported.

## `catalogFormat` (type: `string`):

Detect JSON or CSV automatically, or force one format.

## `recordsPath` (type: `string`):

Optional dot path to the JSON array, such as data.products. Leave empty to detect products, items, records, results, or data arrays.

## `skuField` (type: `string`):

Optional field name or dot path containing the SKU. Automatic detection supports common SKU and id fields.

## `imageField` (type: `string`):

Optional field name or dot path containing the primary image URL. Arrays use their first URL; comma-separated CSV values use the first URL.

## `productNameField` (type: `string`):

Optional field name or dot path containing the product name.

## `productUrlField` (type: `string`):

Optional field name or dot path containing the product page URL.

## `caseSensitive` (type: `boolean`):

Match SKU capitalization exactly. Disabled by default for resilient catalog joins.

## `validateImages` (type: `boolean`):

Follow public redirects and require a successful image content type without downloading image files.

## `validationConcurrency` (type: `integer`):

Number of image URLs validated in parallel.

## Actor input object example

```json
{
  "catalogUrls": [
    {
      "url": "https://dummyjson.com/products?limit=100"
    }
  ],
  "skus": [
    "BEA-ESS-ESS-001",
    "GRO-BRD-CUC-021",
    "KIT-BRD-BOX-051"
  ],
  "catalogFormat": "auto",
  "caseSensitive": false,
  "validateImages": true,
  "validationConcurrency": 10
}
```

# Actor output Schema

## `overview` (type: `string`):

Spreadsheet-ready SKU rows with one primary product image URL per successful match.

# 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 = {
    "catalogUrls": [
        {
            "url": "https://dummyjson.com/products?limit=100"
        }
    ],
    "skus": [
        "BEA-ESS-ESS-001",
        "GRO-BRD-CUC-021",
        "KIT-BRD-BOX-051"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/sku-main-image-url-matcher").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 = {
    "catalogUrls": [{ "url": "https://dummyjson.com/products?limit=100" }],
    "skus": [
        "BEA-ESS-ESS-001",
        "GRO-BRD-CUC-021",
        "KIT-BRD-BOX-051",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/sku-main-image-url-matcher").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 '{
  "catalogUrls": [
    {
      "url": "https://dummyjson.com/products?limit=100"
    }
  ],
  "skus": [
    "BEA-ESS-ESS-001",
    "GRO-BRD-CUC-021",
    "KIT-BRD-BOX-051"
  ]
}' |
apify call automation-lab/sku-main-image-url-matcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/sku-main-image-url-matcher"
        }
    }
}

```

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/gzjHbHGpdomKsU7pg/builds/7VcRD1uVMJfaO2C28/openapi.json
