# Live Shopping Offer Verifier (`nickatnite79/shopping-offer-verifier`) Actor

Check known product URLs against exact SKU, model or GTIN. Return JSON identity checks, disclosed price and stock evidence; unknown shipping and taxes stay unknown.

- **URL**: https://apify.com/nickatnite79/shopping-offer-verifier.md
- **Developed by:** [Nicholas Tamagni](https://apify.com/nickatnite79) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 selected priced-offer checks

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

## Live Shopping Offer Verifier

Use this Actor after a search or catalog tool has supplied a product URL, when an AI agent must check that the page matches an exact requested SKU, model, GTIN, variant or condition before recommending it. It returns machine-readable `status`, `checks`, `reasons` and `evidence`, together with disclosed price, stock, seller and shipping information.

Choose it for validating already-discovered offers, checking a procurement list against specified product identities, or rechecking known URLs for explicit stock/identity conflicts. It does not discover products from search keywords. Supply the exact fields that matter: a brand-only input checks the brand and cannot establish an exact model match.

Current Store pricing is **$0.01 per selected priced-offer check**. A mismatching offer can be billed because it delivers an observed comparison; blocked, ambiguous and missing-price diagnostics have no custom result charge. Check the Store Pricing tab for current terms and the billing details below.

It reads public HTML and structured product data. It does not click a checkout, execute browser JavaScript, log in, solve CAPTCHA, reserve inventory or purchase anything. Retailer access and markup change; this package does not promise full coverage of either retailer.

### Input

```json
{
  "offers": [
    {
      "url": "https://www.adafruit.com/product/4295",
      "expected": {
        "sku": "4295"
      },
      "quantity": 1
    }
  ],
  "country": "US",
  "expectedCurrency": "USD"
}
```

The example checks a real Adafruit product page; stock and prices may change. `offers` accepts 1–100 requests. Each request may include `model`, `sku`, `gtin`, `brand`, `condition`, and a `variant` object. Provide a specific model, SKU or GTIN for a strong product identity check. A brand-only request verifies only that brand; it cannot establish an exact model. Include every field that matters to your use case.

Identifiers are compared as complete normalized values: Unicode normalization, case folding and punctuation/whitespace removal. `EXAMPLE-100` matches `example 100`; it does not match `EXAMPLE-100-PRO`. Product titles are never scanned for a model substring. Brand and variant fields also require complete normalized matches. Conditions normalize common Schema.org values such as `NewCondition`, `UsedCondition` and `RefurbishedCondition`.

`quantity` defaults to 1. A larger quantity requires explicit numeric inventory sufficient for that quantity; `InStock` alone does not imply that two units are available. `country` selects a disclosed shipping destination, and does not change request geolocation. `expectedCurrency` requires a matching explicit currency; no currency is inferred from a retailer domain or shipping country.

### Call from an AI agent

The Actor name is `Nickatnite79/shopping-offer-verifier`. Through Apify MCP, inspect its details and current pricing, supply the Input example, run it by name, and retrieve the dataset. Preserve the status, requested checks, evidence and unknown fields when passing the observation to another agent.

With `apify-client` installed and the caller's credentials configured as `APIFY_TOKEN`:

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('Nickatnite79/shopping-offer-verifier').call({
  offers: [{ url: 'https://www.adafruit.com/product/4295', expected: { sku: '4295' }, quantity: 1 }],
  country: 'US',
  expectedCurrency: 'USD',
}, { maxTotalChargeUsd: 0.01 });
if (run.status !== 'SUCCEEDED') throw new Error(`Actor run status: ${run.status}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

This caps one selected priced-offer check at the current event price. It does not guarantee a matching or available product: the same preset previously returned an explicit out-of-stock `mismatch`. Prices and stock can change. Inspect each result rather than treating run success as product verification.

### Supported evidence

| Source | Adapter behavior | Practical limits |
| --- | --- | --- |
| Best Buy (`bestbuy.com`, `www.bestbuy.com`) | Reads Product/Offer JSON-LD; selects by the page URL or explicit Best Buy SKU in a legacy `.p` URL, modern `/sku/<id>` URL, `/product/.../<numeric>` URL or `skuId` parameter. | No browser rendering or undocumented API fallback. Missing model, condition, shipping or stock remains unknown. |
| Walmart (`walmart.com`, `www.walmart.com`) | Reads Product/Offer JSON-LD. If absent, reads only `__NEXT_DATA__.props.pageProps.initialData.data.product`, matching the page URL/item ID. | Marketplace seller data is reported only when explicitly attached to the selected offer. Embedded payload changes, region restrictions and bot checks can prevent verification. |
| Other public sites | Bounded Product/Offer JSON-LD fallback, including graph references. | A valid public page must provide a selected product and offer in its returned HTML. No universal site adapter is claimed. |

Live coverage observed on 2026-10-13 (build 1.0.10): Adafruit and Shopify storefronts (Allbirds, Kylie Cosmetics) returned selected SKU, price and stock from JSON-LD. Large retailers fetched from Apify's datacenter network returned `unknown` without charge: Best Buy timed out, Walmart served a CAPTCHA, Home Depot, Lowe's, Newegg and B\&H returned HTTP 403, and Apple's product page exposed no supported product data. Adapter recognition alone does not establish working coverage.

Model evidence comes from `model`, `modelNumber`, or `mpn`; Walmart's embedded path also supports `manufacturerProductId`. SKU and GTIN come from explicit structured identifiers. Variants use `color`, `size`, `material`, `pattern`, or named `additionalProperty` values. Missing requested fields remain unknown rather than being inferred from photos, titles, URL text or nearby recommendations.

The Actor selects a product tied to the final page URL, or a matching explicit retailer identifier. Apex and `www` forms of the same host/path are equivalent for page selection. Fragment-only graph references are not page identities, and a conflicting explicit Product URL takes precedence over graph or offer links. A single product with no conflicting URL can be selected as a fallback. Multiple products/variants with no unique page association remain unverified unless an exact requested SKU, GTIN or model identifies exactly one of them (for example a Shopify `ProductGroup.hasVariant` size); if every variant discloses that identifier and none matches, the reason is `no_variant_matches_expected_identifier`. Captcha loader scripts embedded in storefront pages are not treated as challenges; rendered challenge elements are. Products explicitly linked to a different page are not treated as the requested item. An `AggregateOffer.lowPrice` is not a selected purchase price. Multiple seller/price offers stay ambiguous unless exactly one is tied to the requested page URL; the Actor never chooses the cheapest offer by assumption.

### Results and confidence

One dataset row is returned per requested URL unless the run reaches its paid result budget. `observedAt` is the request observation time in UTC. Results reflect public structured data, which may be stale relative to checkout or local inventory.

| Status | Meaning | Agent handling |
| --- | --- | --- |
| `verified` | At least one requested product identifier was checked; all requested fields match; a selected offer has an explicit numeric price, currency and `InStock` evidence. | Use the disclosed match/price/stock evidence for the requested fields; preserve separate shipping and tax uncertainty. |
| `mismatch` | An explicit requested field conflicts, stock is explicitly unavailable/preorder/backorder, requested quantity is insufficient, or a selected shipping disclosure says delivery is unavailable. | Exclude the offer from exact-match recommendations or surface the concrete conflict using `checks` and `reasons`. |
| `unverified` | A product was parsed, but requested evidence, selected variant/seller/offer, price/currency or stock is insufficient. Missing information is not a conflict. | Obtain the missing evidence from another source or keep the offer unresolved. |
| `unknown` | Request/access failure, CAPTCHA/login, no supported product data, or parsing limits prevent a reliable observation. | Treat as an unsuccessful observation, not proof that the item is unavailable or mismatching. |

When a product is selected, requested comparisons have a `checks[].result` of `match`, `mismatch` or `unknown`. A missing requested value is explicitly `unknown` with `actual: null`; it is never silently accepted or converted into a mismatch. Access failures and product-selection ambiguity may have no field checks, so inspect the top-level status and `reasons` first. Preserve nullable price/shipping fields and evidence sources when passing results to another agent. Do not convert unknown shipping to zero, unknown stock to in-stock, or `totalBeforeTax` to a checkout total.

`verified` applies only to fields you requested and the selected offer's disclosed price/stock. Shipping can still be unknown. It does not certify checkout, authenticity, delivery date, local stock, tax treatment or final payable amount.

Shipping is `unknown`, `free`, `paid`, `conditional` or `unavailable`, with nullable `amount` and `currency`, disclosed `conditions`, and the observed options. Only an explicit zero shipping rate can produce `free`. Destination restrictions, minimum-spend requirements, membership tiers and regional/postal constraints remain attached to the result. A fee without a currency is reported, but cannot contribute to a total. More than one applicable option requires further selection and remains unknown. Taxes are always `unknown` because this Actor does not verify checkout taxes.

`subtotal` is disclosed item price × requested quantity. `totalBeforeTax` is returned only when a single unconditional disclosed shipping rate is usable in the same currency (or is explicitly zero). It is never a final landed cost. No fee is invented when shipping is absent.

Representative fixture-derived output (shortened):

```json
{
  "url": "https://www.bestbuy.com/site/example/1234567.p",
  "finalUrl": "https://www.bestbuy.com/site/example/1234567.p",
  "adapter": "bestbuy",
  "status": "verified",
  "reasons": [],
  "observedAt": "2026-10-11T00:00:00.000Z",
  "product": { "model": "EXAMPLE-100", "sku": "1234567", "brand": "Example", "variant": { "color": "Black" }, "condition": "new" },
  "price": { "amount": 129.99, "currency": "USD" },
  "seller": { "name": "Best Buy", "id": null },
  "availability": { "status": "in_stock", "raw": "https://schema.org/InStock", "inventory": null },
  "shipping": { "status": "unknown", "amount": null, "currency": null, "conditions": [], "options": [], "reason": "shipping_not_disclosed" },
  "taxes": { "status": "unknown", "amount": null, "reason": "checkout_taxes_not_verified" },
  "subtotal": 129.99,
  "totalBeforeTax": null,
  "checks": [{ "field": "model", "expected": "EXAMPLE-100", "actual": "EXAMPLE-100", "result": "match" }],
  "evidence": [{ "field": "model", "source": "json-ld[0]", "value": "EXAMPLE-100" }]
}
```

The full result includes every requested check and evidence records for price, stock, seller, condition and shipping. CAPTCHA, HTTP 401/403/429 and login pages take precedence over any stale Product data included in the same HTML.

### Billing

The current price is $0.01 for each custom pay-per-event `result`: 100 chargeable offer checks cost $1 in result events. A row is chargeable only when a product and an individual offer are uniquely selected and an explicit usable price/currency is parsed. Matching, conflicting and incomplete requested identity checks can all be chargeable: the delivered service is the observed offer evidence, not a guaranteed match. An explicitly out-of-stock selected priced offer is a chargeable `mismatch`. Configure the event as described in [PUBLISHING.md](PUBLISHING.md); current Store pricing takes precedence over these examples.

Access errors, missing structured product data, product/offer ambiguity, missing price/currency and parse-limit diagnostics are not charged by that event. These rows still appear in the dataset. `_billable` is an internal core flag and is removed by the runtime before publishing dataset records. Apify infrastructure charges, when applicable, follow the platform's pricing. Configure only this event; combining it with automatic dataset-item charging would duplicate charges and is rejected by the runtime.

### Local development and limits

```sh
npm install
npm test
npm start
```

The Actor uses Node 24, Apify SDK 3 and a direct HTTP client. Root `src/main.js`, `src/runtime.js` and `src/http.js` are included in the deployable folder. Local Apify input can be placed in `storage/key_value_stores/default/INPUT.json`. Tests inject fixture HTTP responses and do not depend on a live retailer or account.

An optional live smoke script is available as `node test/live-smoke.mjs`; it requests current Best Buy, Walmart and Adafruit examples and prints only result summaries. Live requests are separate from deterministic fixtures. On 2026-10-13 a cloud run of 7 checks produced 5 `result` events: Allbirds variant SKU `A12498M085` → `verified` (USD 100, in stock); a non-existent Allbirds SKU → `unverified` with `no_variant_matches_expected_identifier` (not charged); Kylie Cosmetics SKU `KF202` → `verified` (USD 66) and a wrong SKU → `mismatch`; Adafruit `4295` → `mismatch` with `not_in_stock` (USD 38.50, explicit out-of-stock); Adafruit page `4296` checked against SKU `4295` → `mismatch` with `expected_fields_mismatch`; Best Buy → `unknown` (`request_failed`, not charged). Shipping and taxes remained unknown. These observations are snapshots, not ongoing availability guarantees.

Requests are sequential, with HTTP timeouts and bounded retries supplied by the shared client. Initial and redirect destinations must resolve to public network addresses. Environment HTTP/HTTPS proxy configuration is supported by the client; no input field claims to change retailer geography. A proxy does not bypass CAPTCHA or authentication.

Limits are 100 input URLs, 5 MiB returned HTML per page, 100 JSON-LD scripts, 1 MiB per structured script, 5,000 visited structured nodes, depth 20, 100 product nodes and 20 shipping options. Exceeding product parse limits yields a nonchargeable unknown result. Exceeding shipping-option limits leaves shipping unknown. Pages whose useful content requires JavaScript, cookies, account access, postal-code selection or geolocation may return unverified/unknown results. Retailer adapters require maintenance as markup changes.

### Live example run

This is real output from a cloud run of the Actor's default (prefilled) input on 2026-10-16. Just click **Start** to reproduce it.

- Full example dataset (public, JSON): https://api.apify.com/v2/datasets/0EOOb1Lw2sO9Bsg37/items?clean=1\&format=json

Input:

```json
{
  "offers": [
    {
      "url": "https://www.adafruit.com/product/4295",
      "expected": {
        "sku": "4295"
      },
      "quantity": 1
    }
  ],
  "country": "US",
  "expectedCurrency": "USD"
}
```

Output (first item; long arrays shortened):

```json
{
  "index": 0,
  "url": "https://www.adafruit.com/product/4295",
  "requested": {
    "expected": {
      "sku": "4295"
    },
    "quantity": 1,
    "country": "US",
    "currency": "USD"
  },
  "observedAt": "2026-10-16T00:21:28.269Z",
  "taxes": {
    "status": "unknown",
    "amount": null,
    "reason": "checkout_taxes_not_verified"
  },
  "finalUrl": "https://www.adafruit.com/product/4295",
  "adapter": "json-ld",
  "status": "mismatch",
  "reasons": [
    "not_in_stock"
  ],
  "product": {
    "name": "Raspberry Pi 4 Model B - 1 GB RAM",
    "model": null,
    "sku": "4295",
    "gtin": null,
    "brand": null,
    "variant": {},
    "condition": "new"
  },
  "price": {
    "amount": 38.5,
    "currency": "USD"
  },
  "seller": {
    "name": null,
    "id": null
  },
  "availability": {
    "status": "out_of_stock",
    "raw": "http://schema.org/OutOfStock",
    "inventory": null
  },
  "shipping": {
    "status": "unknown",
    "amount": null,
    "currency": null,
    "conditions": [],
    "options": [],
    "reason": "shipping_not_disclosed"
  },
  "subtotal": 38.5,
  "totalBeforeTax": null,
  "checks": [
    {
      "field": "sku",
      "expected": "4295",
      "actual": "4295",
      "result": "match"
    },
    {
      "field": "currency",
      "expected": "USD",
      "actual": "USD",
      "result": "match"
    }
  ],
  "evidence": [
    {
      "field": "name",
      "source": "json-ld[1]",
      "value": "Raspberry Pi 4 Model B - 1 GB RAM"
    },
    {
      "field": "sku",
      "source": "json-ld[1]",
      "value": "4295"
    },
    {
      "field": "price",
      "value": {
        "amount": 38.5,
        "currency": "USD"
      },
      "source": "json-ld[1].offers"
    },
    "…"
  ]
}
```

# Actor input Schema

## `offers` (type: `array`):

Between 1 and 100 product page requests. Include at least one of model, SKU, GTIN or brand to obtain verified status. Greater-than-one quantities require explicit inventory evidence.

## `country` (type: `string`):

Two-letter country code used to select disclosed shipping options. It does not select a proxy or change retailer geolocation.

## `expectedCurrency` (type: `string`):

Three-letter currency code. Missing page currency remains unknown; currencies are never inferred from the retailer or country.

## Actor input object example

```json
{
  "offers": [
    {
      "url": "https://www.adafruit.com/product/4295",
      "expected": {
        "sku": "4295"
      },
      "quantity": 1
    }
  ],
  "country": "US",
  "expectedCurrency": "USD"
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "offers": [
        {
            "url": "https://www.adafruit.com/product/4295",
            "expected": {
                "sku": "4295"
            },
            "quantity": 1
        }
    ],
    "country": "US",
    "expectedCurrency": "USD"
};

// Run the Actor and wait for it to finish
const run = await client.actor("nickatnite79/shopping-offer-verifier").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 = {
    "offers": [{
            "url": "https://www.adafruit.com/product/4295",
            "expected": { "sku": "4295" },
            "quantity": 1,
        }],
    "country": "US",
    "expectedCurrency": "USD",
}

# Run the Actor and wait for it to finish
run = client.actor("nickatnite79/shopping-offer-verifier").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 '{
  "offers": [
    {
      "url": "https://www.adafruit.com/product/4295",
      "expected": {
        "sku": "4295"
      },
      "quantity": 1
    }
  ],
  "country": "US",
  "expectedCurrency": "USD"
}' |
apify call nickatnite79/shopping-offer-verifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nickatnite79/shopping-offer-verifier"
        }
    }
}
```

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/B9e2EpcZAzNSBU2oE/builds/nUE29NdCmyIk6h9aV/openapi.json
