# Sonny Angel Restock Tracker — Stock Checker & Restock Alerts (`scrapelabmax/sonny-angel-restock-tracker`) Actor

Get a Discord or webhook alert the moment Sonny Angel figures restock on the official US store or any Shopify store. Tracks every series and box variant, diffs each run against the last, and outputs only what changed. Schedule it every few minutes for a Shopify inventory monitor.

- **URL**: https://apify.com/scrapelabmax/sonny-angel-restock-tracker.md
- **Developed by:** [Scrapelab Max](https://apify.com/scrapelabmax) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 store 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

## Sonny Angel Restock Tracker: Stock Checker & Restock Alerts

Get a **Discord ping the moment Sonny Angel figures restock**. This tool checks the official Sonny Angel USA store at [sonnyangelusa.com](https://www.sonnyangelusa.com) and up to 10 authorized retailers. Each run compares the catalog with the previous run and reports only what changed. Schedule it every few minutes and you have a restock alert service for every series and box size.

It also works on **any Shopify store**: paste a store or collection URL to use it as a general Shopify restock and inventory monitor.

### What you get

- **Restock alerts in Discord:** product name, box size, price, store, and a direct link to the product page, sent when an item goes from sold out to in stock. Alerts also fire when a new product appears already in stock.
- **Any other destination through a webhook:** a JSON POST to Zapier, Make, n8n, your own server, or a bot.
- **Every box size tracked separately:** "1 Piece" and "Box of 12" restock independently, so each is tracked on its own.
- **Series names:** "Snack Series" becomes `Snack` and "HIPPERS - Harvest Series" becomes `HIPPERS Harvest`. Filter to only the series you care about.
- **Change-only output:** the dataset holds only restocks, sell-outs, new listings, and removals. Turn on `fullCatalog` to get every item on every run.
- **Fails loudly:** if a store returns nothing, the run fails with a clear message. It never reports "everything sold out" because of a hiccup.

### Stores

| Store | How to enable |
|---|---|
| Sonny Angel USA (official), sonnyangelusa.com | Default |
| Kinokuniya USA, JapanLA, Senpai's Corner, Friends NYC, Gunnzo, Leanna Lin's Wonderland, World of Mirth, Peace Love & Happiness Club, Kiddywampus, Colorful Cute | Tick them under **Also check these retailers**. All are on Sonny Angel's official retailer list. |
| Any other Shopify store | Add its URL, or a `/collections/<handle>` URL, to **Store URLs** |

### Quick start

1. Create a Discord webhook: open **Channel settings**, then **Integrations**, **Webhooks**, **New Webhook**, and **Copy Webhook URL**.
2. Paste it into **Discord webhook URL** and tick any extra retailers you want.
3. Click **Start** once. The first run is a baseline: it records the current stock and sends no alerts.
4. Open **Schedules** and run it every **2–5 minutes**. Every later run alerts on changes.

Keep **Snapshot store name** the same across scheduled runs. That store remembers the previous run's stock. If you change it, the next run starts from a new baseline.

### Input

| Field | Default | Description |
|---|---|---|
| `storeUrls` | `["https://www.sonnyangelusa.com"]` | Shopify stores, or single collections, to check |
| `retailerPresets` | `[]` | Retailer ids to add, e.g. `["japanla", "kinokuniya-usa"]` |
| `brandKeyword` | `Sonny Angel` | Track only products whose title, tags, vendor, or type contain this keyword. Leave empty to track everything. |
| `seriesFilter` | `[]` | Keywords such as `["hippers", "animal"]`. Leave empty to track every series. |
| `fullCatalog` | `false` | Output every item each run, not only changes |
| `alertOnNewProducts` | `true` | Also alert when a never-seen product appears in stock |
| `discordWebhookUrl` | — | Discord channel webhook |
| `webhookUrl` | — | Generic JSON webhook |
| `snapshotStoreName` | `sonny-angel-snapshot` | Named key-value store holding the last run's stock |
| `proxyConfiguration` | off | Turn on Apify Proxy only if runs start failing with 403 or 429 |

### Output

One row per variant:

```json
{
  "title": "Snack Series",
  "variantTitle": "Sonny Angel Mini Figure Snack (1 Piece)",
  "series": "Snack",
  "changeType": "restocked",
  "available": true,
  "previousAvailable": false,
  "price": 12.25,
  "currency": "USD",
  "url": "https://www.sonnyangelusa.com/products/snack-series?variant=47033562398934",
  "imageUrl": "https://cdn.shopify.com/...",
  "storeUrl": "https://www.sonnyangelusa.com",
  "productId": "9036689375446",
  "variantId": "47033562398934",
  "sku": "W-SAS-65985ea",
  "tags": ["Sonny Angel", "Regular", "..."],
  "isBaselineRun": false,
  "scrapedAt": "2026-10-27T03:40:12.000Z"
}
```

`changeType` is one of `new`, `restocked`, `soldOut`, `unchanged` (only with `fullCatalog`), or `removed`. Each run's summary is saved in the run's key-value store as `OUTPUT`.

The generic webhook receives `{ "event": "restock", "storeUrls": [...], "scrapedAt": "...", "items": [ ...rows ] }`.

### Cost

These figures were measured on the Apify platform at 256 MB of memory:

| Setup | Run time | Compute per run | Every 2 min, per month |
|---|---|---|---|
| Official store only | about 2–3 s | about 0.0002 CU | about 4 CU |
| Official store plus 10 retailers | about 8 s | about 0.0006 CU | about 12 CU |

Use **256 MB** of memory; more does not make it faster. No proxy is needed by default.

### Good to know

- **"In stock" means reported in stock.** The store's own stock flag can lag behind checkout, and per-customer limits still apply. The alert gets you to the page fast; it cannot guarantee checkout.
- **Polling interval:** 2–5 minutes is the sustainable range. Polling every minute through shared IPs looks like one aggressive client and risks blocks for everyone.
- **What it reads:** only public catalog data, the same JSON the store serves to every visitor. It never touches carts, accounts, or checkout.
- **Stock counts:** inventory quantities aren't published by these stores, so `inventoryQuantity` is usually `null`.

### Feedback

Want a store added, or found a series name that looks wrong? Open an issue on the actor's **Issues** tab.

# Actor input Schema

## `storeUrls` (type: `array`):

Any Shopify store, e.g. <code>https://www.sonnyangelusa.com</code> (the official Sonny Angel USA store, used by default). Paste the home page, or a collection page such as <code>https://shop.com/collections/sonny-angel</code> to poll just that collection (much cheaper on big stores). Bare domains work too. Each store is checked and diffed separately. May be empty if you tick retailers below.

## `retailerPresets` (type: `array`):

Sonny Angel retailers on the official retailer list that run Shopify, polled through their Sonny Angel collection. Each ticked store adds one catalog check per run.

## `brandKeyword` (type: `string`):

Only products whose tags, vendor, product type or title contain this keyword are tracked (case-insensitive). Keep <code>Sonny Angel</code> for multi-brand retailers; enter <code>\*</code> to track every product in the store.

## `seriesFilter` (type: `array`):

Only track products matching any of these keywords, e.g. <code>Hipper</code>, <code>Animal Series</code>, <code>Christmas</code>. Matched case-insensitively against the title, option, series and tags. Leave empty to track every series.

## `fullCatalog` (type: `boolean`):

Off: the dataset only gets rows that changed since the last run (restocked, sold out, new, removed). On: every tracked variant is written on every run, with its change type.

## `alertOnNewProducts` (type: `boolean`):

Also alert when a product that was never seen before shows up already in stock (a new drop). Restocks always alert.

## `discordWebhookUrl` (type: `string`):

Post restock alerts to a Discord channel. In Discord: channel settings → Integrations → Webhooks → New Webhook → Copy Webhook URL. It looks like <code>https://discord.com/api/webhooks/123/abc</code>. Alerts say "reported in stock": the store's stock flag can lag checkout, so a purchase is never guaranteed.

## `webhookUrl` (type: `string`):

Any HTTP(S) endpoint (Zapier, Make, n8n, your own server). When something restocks it receives a JSON POST: <code>{ "event": "restock", "storeUrls": \[...], "scrapedAt": "...", "items": \[...] }</code> where items are the same rows as the dataset.

## `snapshotStoreName` (type: `string`):

Named key-value store in your Apify account that remembers stock between runs. <b>Keep it the same between scheduled runs</b>, or every run starts over as a baseline (no alerts). Use a different name per schedule if you run several configurations. Letters, digits and dashes only.

## `currency` (type: `string`):

Three-letter currency code of the store's prices, e.g. <code>USD</code>, <code>CAD</code>, <code>GBP</code>. Shopify's public catalog does not report it, so set it to match the store.

## `maxPages` (type: `integer`):

Maximum catalog pages fetched per store (250 products each). 10 covers 2,500 products, plenty for most stores.

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

Off by default because Shopify's catalog JSON rarely blocks, so you pay no proxy cost. If runs start failing with 403 or 429 errors, switch to Apify datacenter proxy, or residential as a last resort.

## Actor input object example

```json
{
  "storeUrls": [
    "https://www.sonnyangelusa.com"
  ],
  "retailerPresets": [],
  "brandKeyword": "Sonny Angel",
  "seriesFilter": [],
  "fullCatalog": false,
  "alertOnNewProducts": true,
  "snapshotStoreName": "sonny-angel-snapshot",
  "currency": "USD",
  "maxPages": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `changes` (type: `string`):

No description

## `allFields` (type: `string`):

No description

## `runSummary` (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 = {
    "storeUrls": [
        "https://www.sonnyangelusa.com"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapelabmax/sonny-angel-restock-tracker").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 = {
    "storeUrls": ["https://www.sonnyangelusa.com"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapelabmax/sonny-angel-restock-tracker").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 '{
  "storeUrls": [
    "https://www.sonnyangelusa.com"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call scrapelabmax/sonny-angel-restock-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapelabmax/sonny-angel-restock-tracker"
        }
    }
}
```

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/2V9UmZ0DgdKdfg8mr/builds/PVF65OBFvoGSarp2M/openapi.json
