# noon Seller & Price Monitor - UAE & Saudi Buy Box Tracker (`neverempty/noon-seller-price-monitor`) Actor

Every seller on a noon.com product in the UAE or Saudi Arabia: Buy Box winner, price, was-price, stock, delivery date, noon fulfilment, seller rating, per size or color. Monitor mode returns only changes: Buy Box won or lost, price up or down, new seller, seller gone.

- **URL**: https://apify.com/neverempty/noon-seller-price-monitor.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 offer returneds

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?

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

## noon Seller & Price Monitor - UAE & Saudi Buy Box Tracker

Every seller on a **noon.com** product in the **UAE** or **Saudi Arabia**, with the **Buy Box winner** (the offer that goes into the cart), as clean JSON: seller name and seller ID, price, was-price, stock, estimated delivery date, fulfilled by noon (FBN), free delivery, seller rating - for **every size and color** of the product. In **monitor mode** it returns only what changed since your last run: **Buy Box won or lost, price up or down, a new seller, a seller that left**. Built for noon sellers, brands watching their resellers, and repricing or price-intelligence pipelines that check the same products many times a day and do not want to pay again for offers that did not change.

- **Every seller of every size and color, every run.** No seller is skipped between pages, so `seller-left` means the seller really is gone from the product.
- **One row per seller per size or color**, with the Buy Box fields on every row: `buyBoxSellerId`, `buyBoxSellerName`, `buyBoxPrice`, and in monitor mode `previousBuyBoxSellerId` / `previousBuyBoxPrice`.
- **Numbers, not text.** `price`, `wasPrice`, `discountPercent`, `stock`, `sellerRating`, `sellerRatingsCount`, `sellerPositivePercent` are numbers; `currency` is `AED` or `SAR`; `estimatedDeliveryDate` is an ISO date.
- **Monitor mode with a price threshold.** Set `minPriceChangePercent` to ignore small moves, and `watchName` to keep one memory per watch even when you add or remove products.
- **Nothing is charged for products that could not be read.** A SKU noon does not know, a product with no sellers right now, or a request noon refused comes back as a free row that says why. The first run of a watch is a normal run (every seller, `first-check`); after that you pay only for changes.
- **UAE and Saudi Arabia**, each read with that country's store settings, so prices, sellers and delivery dates are what a shopper in that country sees. A price is reported only when noon's answer itself says which country it is for, and that country is the one you asked for.

Unofficial. No login, no cookies, no seller account and no API keys needed.

### What you get

One row per seller and size or color. Example (local run on a saved noon answer, 2026-10-21, SKU N70211553V on noon UAE):

```json
{
  "status": "ok",
  "changeType": "first-check",
  "changes": ["first-check"],
  "sku": "N70211553V",
  "country": "uae",
  "productTitle": "iPhone 17 256GB (Nano SIM + eSIM) Black 5G With FaceTime - International Version",
  "brand": "Apple",
  "category": "Smartphones",
  "productRating": 4.6,
  "productRatingsCount": 6878,
  "variantSku": "N70211553V-1",
  "variant": null,
  "buyBoxSellerId": "p-16024",
  "buyBoxSellerName": "Viola-UAE",
  "buyBoxPrice": 3399,
  "sellersOnVariant": 3,
  "lowestPrice": 3399,
  "isBuyBoxWinner": true,
  "sellerId": "p-16024",
  "sellerName": "Viola-UAE",
  "price": 3399,
  "currency": "AED",
  "wasPrice": null,
  "discountPercent": null,
  "offersFromSeller": 1,
  "priceRank": 1,
  "isLowestPrice": true,
  "priceAboveBuyBox": 0,
  "stock": 3,
  "isBuyable": true,
  "fulfilledByNoon": true,
  "freeDelivery": true,
  "estimatedDeliveryDate": "2026-10-22",
  "sellerRating": 4.8,
  "sellerRatingsCount": 1390,
  "sellerPositivePercent": 98,
  "warranty": "1 year manufacturer warranty",
  "isReturnable": true,
  "productUrl": "https://www.noon.com/uae-en/iphone-17-256gb-nano-sim-esim-black-5g-with-facetime-international-version/N70211553V/p/",
  "checkedAt": "2026-10-21T06:51:58.105Z"
}
```

| Column | Meaning |
|---|---|
| `status` | `ok` for an offer row. Other values are free rows that say why nothing was returned (below) |
| `changeType` | `first-check`, `new-seller`, `seller-returned` (a seller this watch saw leave or sell out is back), `buybox-won`, `buybox-lost`, `price-down`, `price-up`, `seller-left` or `unchanged` |
| `changes` | All changes for this seller in this run (for example `["buybox-won", "price-down"]`); `changeType` is the first |
| `sku`, `country`, `productTitle`, `brand`, `category`, `productRating`, `productRatingsCount` | The product |
| `variantSku`, `variant` | The size or color (for example `ZA133E7D109B839C34173Z-3`, `41`). `variant` is null for products without sizes or colors |
| `buyBoxSellerId`, `buyBoxSellerName`, `buyBoxPrice` | Who holds the Buy Box for this size or color right now |
| `buyBoxChanged`, `previousBuyBoxSellerId`, `previousBuyBoxSellerName`, `previousBuyBoxPrice` | Monitor mode: whether the Buy Box moved since the last run of this watch, and who had it |
| `sellersOnVariant`, `lowestPrice` | How many sellers offer this size or color, and the lowest price among them |
| `isBuyBoxWinner` | True for the seller holding the Buy Box |
| `sellerId`, `sellerName` | The seller (noon's partner code, for example `p-16024`, and the store name) |
| `price`, `currency`, `wasPrice`, `discountPercent` | The price a shopper pays now, and the crossed-out price with the discount when noon shows one |
| `offersFromSeller` | How many offers this seller has on this size or color (the Buy Box offer or the cheapest one is returned) |
| `previousPrice`, `priceChange`, `priceChangePercent`, `previousCheckedAt` | Monitor mode: the price this watch last reported for this seller, and the difference |
| `priceRank`, `isLowestPrice`, `priceAboveBuyBox` | Position by price among the sellers of this size or color, and the gap to the Buy Box price |
| `stock`, `isBuyable` | Units noon shows as available for this offer, and whether it can be added to the cart |
| `fulfilledByNoon`, `freeDelivery`, `estimatedDeliveryDate` | FBN or seller-fulfilled, free delivery, and the delivery date shown to a shopper |
| `sellerRating`, `sellerRatingsCount`, `sellerPositivePercent` | Seller feedback as shown on the offer |
| `warranty`, `isReturnable` | Warranty text and returns, as shown on the offer |
| `sellerStillListed` | For `buybox-lost` / `seller-left` rows: whether the seller still has an offer on this size or color |
| `productUrl` | The product page in that country |
| `note` | Free rows only: why nothing (or not everything) was returned |
| `watchName`, `checkedAt` | The watch and the time of the check |

#### Free rows (not charged)

| `status` | When |
|---|---|
| `no-change` | Monitor mode: no Buy Box change, no new seller, no reportable price move and no seller gone since the last run |
| `no-offers` | noon shows no seller for the product (or the size or color you asked for) right now |
| `not-found` | noon has no product with this SKU (HTTP 404), or no size or color with the number you gave |
| `blocked` | noon refused the requests. The Actor does not work around it, so it stops; the remaining products are listed in the row |
| `unreadable` | noon's answer could not be read, also after asking again, or it was not for the product or country asked for |
| `more-not-returned` | More rows than `maxOffersPerProduct`; they were not returned and not charged |
| `bad-input` | The input could not be used; nothing was requested |
| `budget-reached` | The run hit the maximum total charge you set; rows not returned are not remembered, so the next run returns them |
| `timeout-near` | The run was close to its timeout, so it stopped before the next product and saved what it had read; the products not checked are listed in the row |
| `example-product` | `products` was empty, so the run checked the example product `N70211553V` (its seller rows are normal, charged rows) |

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `products` | array of strings | example `N70211553V` | SKUs (`N70211553V`), a SKU with a size or color number (`ZA133E7D109B839C34173Z-3`) or product URLs. A URL with `?o=<SKU>-N` checks only that size or color. Up to 100 per run. The same product written several ways (SKU, SKU with a size, URL) is read and charged once; if any of them is the whole product, every size and color is returned. Empty: the example product `N70211553V` is checked and a free `example-product` row says so |
| `country` | string | `uae` | `uae` (noon.com/uae-en, AED) or `saudi` (noon.com/saudi-en, SAR). Product URLs must be for the same country |
| `maxOffersPerProduct` | integer 1-200 | 50 | Offer rows per product (one per seller per size or color) |
| `onlyChanges` | boolean | false | Monitor mode: after the first run, return only changed sellers |
| `minPriceChangePercent` | number 0-50 | 0 | In monitor mode, ignore price moves smaller than this percentage |
| `watchName` | string | (none) | Names the memory of monitor mode. With a name, the memory belongs to the name and the country, so adding or removing products keeps it. Without a name, the memory belongs to this exact list of products and the country: a different list starts again with `first-check` |
| `resetMonitoringState` | boolean | false | Forget this watch's memory at the start of the run |

Example:

```json
{ "products": ["N70211553V", "https://www.noon.com/uae-en/some-product/N12345678A/p/"], "country": "uae", "onlyChanges": true }
```

### How to use it for Buy Box monitoring on noon

1. Run it once with your SKUs to see every seller, every size and color, and who holds the Buy Box.
2. Create a **Schedule** (every 15 or 60 minutes) with the same input and `onlyChanges: true`.
3. Connect a webhook or an integration (Slack, email, Zapier, Make, Google Sheets). Filter on `status = "ok"`; a `buybox-lost` row for your own seller ID is the alert most sellers want.

### Pricing

Pay per event:

- **Run start** - charged once per run, only when at least one product's seller list was actually read. Not charged when every SKU is unknown, when noon refuses the request, or when the run's maximum total charge has no room for the start fee plus one row (then nothing is requested at all).
- **Offer returned** - one per seller row. Free rows are never charged, and in monitor mode unchanged sellers are not returned.

A scheduled check with no change costs one run start. The exact prices are shown on the Pricing tab.

### Good to know

- The Buy Box shown is the one noon shows to a logged-out shopper in that country with the default delivery location.
- For a size or color other than the product's default, the Buy Box is the first offer noon lists for it (the one its page shows first).
- A seller with several offers on the same size or color is returned once (the Buy Box offer, or the cheapest), with `offersFromSeller` telling how many offers it has.
- noon UAE and noon Saudi Arabia only. Each run reads one country; run once per country for both.
- Two overlapping schedules for the same products and `watchName` can overwrite each other's memory; use different `watchName`s.
- When every seller of a product sells out, monitor mode returns `buybox-lost` and `seller-left` once (with `price: null` and `sellerStillListed: false`), then free `no-offers` rows while it stays sold out. When the sellers come back, they are returned as `seller-returned`, not as new sellers.
- If a run is aborted, restarted by Apify or close to its timeout, the seller rows already returned (and charged) are remembered, so the next run does not return them again. Products already checked before a restart are not checked again in the same run.
- If noon's answer does not say which country it is for, the product comes back as a free `unreadable` row instead of prices that might be for the other country.
- If noon refuses the requests, the run stops and says so in a free row. It never solves check pages or CAPTCHAs.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

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

noon products to check: a SKU (N70211553V), a SKU with a size or color number to check only that one (ZA133E7D109B839C34173Z-3), or a product URL (https://www.noon.com/uae-en/.../N70211553V/p/; a URL with ?o=<SKU>-N checks only that size or color). Up to 100 per run. The same product written several ways is read and charged once. Leave empty to run the example product N70211553V (a free row says so).

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

Which noon store to read. Sellers, prices, stock and the Buy Box differ by country. Product URLs must be for the same country.

## `maxOffersPerProduct` (type: `integer`):

How many offer rows to return per product (one row per seller per size or color). noon returns every seller in one answer, so this only caps the rows. Rows beyond this number are not charged.

## `onlyChanges` (type: `boolean`):

On: after the first run, return only offers that changed for the same watch: a new seller, the Buy Box moving to or from a seller, a price going up or down, or a seller that left. Unchanged offers are not returned and not charged; a free no-change row says so. Off: every offer is returned, each with its changeType.

## `minPriceChangePercent` (type: `number`):

In monitor mode, a price move smaller than this percentage is not reported as price-up or price-down. 0 reports every change.

## `watchName` (type: `string`):

Optional. Names the memory of monitor mode: with a name, the memory belongs to the name and the country, so adding or removing products keeps it. Without a name, each exact list of products has its own memory. Letters, digits, dot, dash and underscore, up to 40.

## `resetMonitoringState` (type: `boolean`):

On: forget what this watch remembered, so this run is a first check again.

## Actor input object example

```json
{
  "products": [
    "N70211553V"
  ],
  "country": "uae",
  "maxOffersPerProduct": 50,
  "onlyChanges": false,
  "minPriceChangePercent": 0,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per seller per size or color of each noon product: seller name and ID, price, was-price, stock, estimated delivery date, fulfilled by noon, seller rating, whether this offer holds the Buy Box, the Buy Box winner and price for that size or color, and the change since the last check (changeType, previousPrice, previous Buy Box winner). A product with no offers, no change, a SKU noon does not know, a refused request or a run that hit its maximum charge comes back as a free row that says why.

# 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": [
        "N70211553V"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/noon-seller-price-monitor").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": ["N70211553V"] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/noon-seller-price-monitor").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": [
    "N70211553V"
  ]
}' |
apify call neverempty/noon-seller-price-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/noon-seller-price-monitor"
        }
    }
}
```

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/kbLX7bEri4azfS7bj/builds/a9AorjpaxR5Lw0S8H/openapi.json
