# Catalog Photo Mapping & Readiness QA (`atesen-software/catalog-photo-readiness-qa`) Actor

Offline SKU, required-field and photo-manifest checks with explicit ambiguity review and CSV evidence. No imports, photo downloads or invented merchant facts.

- **URL**: https://apify.com/atesen-software/catalog-photo-readiness-qa.md
- **Developed by:** [Atesen Software](https://apify.com/atesen-software) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 catalog qa rows

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

### What does Catalog Photo Mapping & Readiness QA do?

Find missing catalog fields, duplicate SKU rows and ambiguous photo mappings before a human imports or publishes products. This offline Actor takes a product list and photo-file manifest, returns one QA row per product, and exports a review CSV and ordered photo plan.

The default is five explicitly synthetic products. One passes manifest checks; four need review. No website is scraped, no image is downloaded, and no merchant account is connected. Run time scales with the bounded input; there is no remote request or third-party API cost.

### How to run

1. Open the Input tab and use the five-product synthetic example, or supply your authorized product and photo manifests.
2. Run the Actor, then review each row's issues and status.
3. Download `REVIEW.csv` and `PHOTO-PLAN.csv` from the run's output links. Resolve every review issue and visually inspect actual files before using native merchant tools.

API calls and recurring runs use the same deterministic checks. You can download the dataset in JSON, HTML, CSV or Excel using Apify's dataset export. No proxy or external integration is needed.

### Input

Supply `products` (1–1000 objects) and `photos` (0–5000 objects). Empty `{}` runs the synthetic demonstration. Supplying products without photos uses an empty photo manifest. A custom photo manifest requires products too. Unknown top-level fields, null arrays and wrong types fail validation.

```json
{
  "products": [{"sku":"DEMO-1","name":"Synthetic cup","price":"19.90","photoRefs":["demo-1-front.jpg","demo-1-side.jpg"]}],
  "photos": [{"fileName":"demo-1-front.jpg","sku":"DEMO-1"},{"fileName":"demo-1-side.jpg","sku":"DEMO-1"}]
}
```

Each product accepts `sku`, `name`, `price` as supplied text, optional `photoRefs` as ordered exact filenames, optional `urlIdentifier` and `originalUrlIdentifier`. SKU, name and price are requirements of this QA policy; they are not a claim that every Nuvemshop workflow requires all three. Never supply personal/customer data. Only provide data you are authorized to process.

Photo objects require `fileName` and optionally `sku`. Matching is exact and case sensitive. No fuzzy filename guessing, SKU generation, price estimation, SEO generation or product-description generation occurs. Price text is preserved in JSON; its syntax is reviewed as a nonnegative decimal, without currency or tax inference.

Without explicit `photoRefs`, exactly one photo with the same SKU can be selected. Multiple candidates are ambiguous and none is selected. An explicit ordered gallery resolves multiple photos only when every filename exists once and its declared SKU agrees. Duplicate product SKUs always require human review, including legitimate variants. Missing references, conflicting photo SKUs, duplicate manifest filenames and repeated references block the photo plan for that row.

### Output

Dataset fields: `rowNumber` (integer), `sku`, `name`, `price`, `urlIdentifier`, `status`, `photoFiles` (string array), `photoVerification`, `issueCount` (integer), `issues` (code/field/detail objects). Empty values remain empty strings; no missing fact is manufactured. Schema fields are nullable for resilient Console display.

`manifest_ready` means the deterministic checks passed. It does **not** mean the image exists on disk, resolves over HTTP, depicts the correct product, has suitable licensing, or has been visually approved. All rows explicitly state `manifest_only_not_fetched`. A nonexistent filename reference is a manifest defect; this Actor cannot diagnose a broken web URL.

Key-value-store files:

- `REVIEW.csv`: every input product with status and issue codes.
- `PHOTO-PLAN.csv`: unambiguous mappings only, in supplied gallery order. Product-field defects can remain; check REVIEW before use.
- `SUMMARY`: counts, unassigned photos, `networkRequests: 0`, `importExecuted: false`.
- `STATE`: completion and abort progress.

CSV uses UTF-8 BOM, quoted cells and CRLF. Cells starting with spreadsheet formula markers are prefixed with an apostrophe for safety; original JSON text is preserved. CSV files are review deliverables, **not Nuvemshop-native import files**. There is no automatic upload, account mutation or publication.

### Nuvemshop context and alternatives

The [official CSV field guide, updated 28 October 2026](https://atendimento.nuvemshop.com.br/pt_BR/importar-e-exportar-produtos/como-preencher-os-campos-da-planilha-de-carga-em-massa) says native CSV cannot upload/change/delete images. Changing an existing URL identifier can create a duplicate product; this Actor flags an explicitly supplied original/current identifier mismatch. Native CSV is still the appropriate field importer, and supported image apps handle uploads. This tool only prepares a separate review plan.

[Product Image URLs Matcher](https://apify.com/automation-lab/sku-main-image-url-matcher) already fetches public JSON/CSV feeds, validates image URLs and selects the first usable catalog match. Use it for public feed enrichment. This prototype's narrower purpose is rejecting ambiguous supplied manifests and reviewing catalog completeness offline, without a public feed or first-match selection. A spreadsheet can also perform these checks; the Actor provides repeatable API-shaped QA, not an exclusive capability.

### Status, cost and limits

Experimental utility; no proven Actor buyer demand, merchant acceptance or sales claim. The active platform pricing and any scheduled effective date are shown on the Actor’s Pricing tab. Local runs do not use cloud compute; Apify platform runs consume the caller's available compute/storage quota. There are no proxy, browser, LLM or external API charges. No actual job materials or brands appear in the demonstration.

No network input, pagination or retries. Limits are 1000 products, 5000 photos, 50 references per product and 500 characters per scalar. Invalid structure fails before output; valid structure containing business-data problems returns review issues. Human visual review and native-template validation remain necessary. Support: public Actor issue page after publication.

### Pricing and spending limits

The supported pay-per-event unit is **USD 0.002 per delivered QA dataset row** (USD 2/1000; five demonstration rows cost USD 0.01 when this pricing is active). Check the Pricing tab for the active model and any scheduled effective date. Until a scheduled change takes effect, the current pricing remains in force. There is no separate start, synthetic dataset-item or third-party API fee configured by this source. The price is an experimental offer; buyer willingness to pay is unverified.

A billable unit is one accessible dataset QA row, including a row reporting missing fields or ambiguous photos. It is not a successful product import or ready-to-publish merchandise. Spending limits apply per run. A budget below one row produces no billable rows and header-only CSVs; a partial budget includes the final affordable row and then stops. Non-PPE runs retain the full output behavior.

In PPE mode, CSVs and summary counts contain only rows whose custom `qa-row` charge was confirmed. The summary states `inputProductCount`, `chargedQaRows` and `budgetLimited`; `unassignedPhotos` is null with an explicit scope because the remaining catalog is not reported. Filenames and issue details from the undelivered remainder are excluded. In non-PPE mode, the summary retains the full supplied-manifest diagnostics.

Dataset delivery, billing and CSV writes are separate operations. If a later CSV write fails, delivered dataset rows remain available as the billed unit and the run fails at the export stage. No automatic refund is implemented or promised; report the failed run for support review. If a charge request itself fails after a dataset write, a row may already exist in the dataset but is excluded from confirmed-row CSV output. A failed run is not reported as successful delivery.

Actor-owner synthetic tests and free-plan usage are not customer sales or proof of creator income. See the [official PPE limits](https://docs.apify.com/actors/monetize/pay-per-event) and [pricing change rules](https://docs.apify.com/actors/monetize/set-up-monetization).

# Actor input Schema

## `products` (type: `array`):

1-1000 objects: sku,name,price supplied as text; optional photoRefs ordered filenames,urlIdentifier,originalUrlIdentifier. SKU/name/price are QA requirements, not universal platform rules. Missing field values are reported, never generated.

## `photos` (type: `array`):

0-5000 objects: fileName required; optional exact case-sensitive sku. This inventory does not prove files exist, resolve or depict the correct product. No URLs are fetched.

## Actor input object example

```json
{
  "products": [
    {
      "sku": "DEMO-001",
      "name": "Synthetic sample cup",
      "price": "19.90",
      "photoRefs": [
        "demo-001-front.jpg"
      ]
    },
    {
      "sku": "",
      "name": "Synthetic sample bowl",
      "price": "12.00",
      "photoRefs": [
        "bowl.jpg"
      ]
    },
    {
      "sku": "DEMO-003",
      "name": "",
      "price": "",
      "photoRefs": [
        "missing.jpg"
      ]
    },
    {
      "sku": "DEMO-004",
      "name": "Synthetic sample plate",
      "price": "24.50"
    },
    {
      "sku": "DEMO-004",
      "name": "Synthetic duplicate plate",
      "price": "24.50"
    }
  ],
  "photos": [
    {
      "fileName": "demo-001-front.jpg",
      "sku": "DEMO-001"
    },
    {
      "fileName": "bowl.jpg",
      "sku": ""
    },
    {
      "fileName": "plate-a.jpg",
      "sku": "DEMO-004"
    },
    {
      "fileName": "plate-b.jpg",
      "sku": "DEMO-004"
    }
  ]
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `reviewCsv` (type: `string`):

No description

## `photoPlan` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `state` (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 = {
    "products": [
        {
            "sku": "DEMO-001",
            "name": "Synthetic sample cup",
            "price": "19.90",
            "photoRefs": [
                "demo-001-front.jpg"
            ]
        },
        {
            "sku": "",
            "name": "Synthetic sample bowl",
            "price": "12.00",
            "photoRefs": [
                "bowl.jpg"
            ]
        },
        {
            "sku": "DEMO-003",
            "name": "",
            "price": "",
            "photoRefs": [
                "missing.jpg"
            ]
        },
        {
            "sku": "DEMO-004",
            "name": "Synthetic sample plate",
            "price": "24.50"
        },
        {
            "sku": "DEMO-004",
            "name": "Synthetic duplicate plate",
            "price": "24.50"
        }
    ],
    "photos": [
        {
            "fileName": "demo-001-front.jpg",
            "sku": "DEMO-001"
        },
        {
            "fileName": "bowl.jpg",
            "sku": ""
        },
        {
            "fileName": "plate-a.jpg",
            "sku": "DEMO-004"
        },
        {
            "fileName": "plate-b.jpg",
            "sku": "DEMO-004"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("atesen-software/catalog-photo-readiness-qa").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 = {
    "products": [
        {
            "sku": "DEMO-001",
            "name": "Synthetic sample cup",
            "price": "19.90",
            "photoRefs": ["demo-001-front.jpg"],
        },
        {
            "sku": "",
            "name": "Synthetic sample bowl",
            "price": "12.00",
            "photoRefs": ["bowl.jpg"],
        },
        {
            "sku": "DEMO-003",
            "name": "",
            "price": "",
            "photoRefs": ["missing.jpg"],
        },
        {
            "sku": "DEMO-004",
            "name": "Synthetic sample plate",
            "price": "24.50",
        },
        {
            "sku": "DEMO-004",
            "name": "Synthetic duplicate plate",
            "price": "24.50",
        },
    ],
    "photos": [
        {
            "fileName": "demo-001-front.jpg",
            "sku": "DEMO-001",
        },
        {
            "fileName": "bowl.jpg",
            "sku": "",
        },
        {
            "fileName": "plate-a.jpg",
            "sku": "DEMO-004",
        },
        {
            "fileName": "plate-b.jpg",
            "sku": "DEMO-004",
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("atesen-software/catalog-photo-readiness-qa").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 '{
  "products": [
    {
      "sku": "DEMO-001",
      "name": "Synthetic sample cup",
      "price": "19.90",
      "photoRefs": [
        "demo-001-front.jpg"
      ]
    },
    {
      "sku": "",
      "name": "Synthetic sample bowl",
      "price": "12.00",
      "photoRefs": [
        "bowl.jpg"
      ]
    },
    {
      "sku": "DEMO-003",
      "name": "",
      "price": "",
      "photoRefs": [
        "missing.jpg"
      ]
    },
    {
      "sku": "DEMO-004",
      "name": "Synthetic sample plate",
      "price": "24.50"
    },
    {
      "sku": "DEMO-004",
      "name": "Synthetic duplicate plate",
      "price": "24.50"
    }
  ],
  "photos": [
    {
      "fileName": "demo-001-front.jpg",
      "sku": "DEMO-001"
    },
    {
      "fileName": "bowl.jpg",
      "sku": ""
    },
    {
      "fileName": "plate-a.jpg",
      "sku": "DEMO-004"
    },
    {
      "fileName": "plate-b.jpg",
      "sku": "DEMO-004"
    }
  ]
}' |
apify call atesen-software/catalog-photo-readiness-qa --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atesen-software/catalog-photo-readiness-qa"
        }
    }
}
```

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/koJKwBmxidQRvllUM/builds/bGD7PvKRXDHSxriEZ/openapi.json
