# Restock Margin Guard (`afkdigital/restock-margin-guard`) Actor

Review your own SKU reorder quantity and verified landed gross margin against the required target. Explicit missing costs, MOQ, pack rounding and manual approval.

- **URL**: https://apify.com/afkdigital/restock-margin-guard.md
- **Developed by:** [AFK Digital](https://apify.com/afkdigital) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 actionable restock quote revieweds

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

## Restock Margin Guard

**Check your own SKU resale margin against a supplier reorder offer.** Your procurement officer reviews every result; this Actor never buys, ranks sellers, scrapes catalogs or submits purchase orders.

### Run with your own records

Choose exactly ONE product-list source and ONE quote source:

- `products` / `quotes`: JSON arrays, ideal for integrations.
- `productsCsv` / `quotesCsv`: pasted UTF-8 CSV export text.
- `productsFile` / `quotesFile`: file uploads yielding an authorized Apify Key-Value Store record URL. No third-party hosting/link fetching is supported. Apify storage access must permit the running Actor to read the records. If file permissions prevent a run, paste CSV/JSON data directly.

The ready-to-run `examples/input.json` contains **fully fictional** data and covers a known-cost quote, missing freight, unavailable FX, and an expired quote. CSV headings are in `examples/products.csv` and `examples/quotes.csv`.

**Products** have `sku` (unique), `salePrice` (>0, **excluding sales VAT**), `currency` (uppercase ISO currency), `saleUnit` (e.g. `each`), and optional `targetMarginPct` overriding the global target.

**Quotes** have `quoteId` (unique source identifier), `sku`, `supplier`, `currency`, `purchaseUnit`, `moq`, `packSize`, `validUntil` (YYYY-MM-DD inclusive), `tiers` array of `{minQty,unitPrice}`, and `costs` object. For a `case` price on products sold by `each`, supply `unitsPerPurchaseUnit`, e.g. 12. Different units with no explicit conversion are incomparable. Unknown MOQ or pack size prevents a verified total cost. CSV uses a quoted JSON string in `tiersJson`, or a single `unitPrice` for a single price tier.

**Quote expenses** (quote currency, for the MOQ/pack-rounded quantity) are `freightTotal`, EITHER `nonRecoverableTaxTotal` OR `nonRecoverableTaxRatePct` (percentage of merchandise total), `otherLandedTotal`, and `salesCommissionPct`. For each, use **0 only when you have verified zero cost**. An omitted fee is unknown; it is not assumed to be zero. Recoverable VAT does not belong in `nonRecoverableTax`. Selling commission is not part of gross COGS and has a separate illustrative after-commission margin.

**Settings**: `requestedQuantity` in your product's sale units, global `targetMarginPct`, `targetCurrency`, `asOf` UTC date (defaults to current UTC date), `maxResults` (1–500), and optional `fxRates` such as `{"CNY":"0.14"}`, meaning one CNY equals 0.14 target-currency units. Currencies with no supplied FX are **incomparable**; the Actor does not retrieve live or historical rates.

### How the calculations work

1. Compute base purchase quantity = ceil(desired sale units / confirmed units per purchase unit).
2. Raise to the MOQ, then round up to a multiple of `packSize`. Show applied purchase units, equivalent sale units and surplus. The unit price comes from the **highest all-units tier whose minQty is no greater than actual purchased units**; graduated/marginal tiers are NOT automatically supported.
3. Merchandise subtotal = applied purchase quantity × quoted price, converted to report currency with customer FX. Known fees are added to a clearly labelled `knownCostSubtotal`.
4. If purchase quantity and all procurement fees are confirmed: `estimatedTotalCost = (merchandise + freight + nonrecoverable tax + other landed expenses) × FX`; `verifiableUnitCost = estimatedTotalCost / appliedSaleQuantity`.
5. `grossMarginPct = 100 × (net retail unit price − landed unit cost) / net retail unit price`; `targetGapPercentagePoints = grossMarginPct − targetMarginPct`. `afterCommissionMarginPct = grossMarginPct − salesCommissionPct` (optional, not net profit).
6. If freight, procurement tax or other landed expense is unknown, **verifiable unit cost, total cost, gross margin and target gap are null**; `indicativeGrossMarginCeilingPct` is only an optimistic ceiling based on known costs. If sales commission alone is unknown, gross margin may still be valid, but after-commission margin stays null.

Date validation is by UTC **calendar date**, quote valid through `validUntil`. Output statuses: `comparable` (arithmetic supported), `incomplete` (expenses/quantity/validity missing), `expired` (old offer), `incomparable` (FX, unit or price ambiguity). Every row sets `requiresHumanReview: true` and `decision: human_review_only`; even a positive margin gap **does not** authorize purchasing.

### Results and pricing

Dataset fields include `sku`, `supplier`, `sourceQuoteId`, requested/applied quantities, tier, target currency, quoted unit cost, verifiable landed unit cost/total when available, gross margin, target gap, missing expense types, currency/unit/expiration flags and human-review marker. The `SUMMARY` key-value record counts result types, actual saved rows, budget stop condition and billing events.

Planned paid events: **USD 0.001 per eligible stored quote analysis**, plus **USD 0.00005 per Actor start**. Incomparable/unusable quote rows have no per-quote fee; expired/incomplete-but-usable rows are chargeable. For 10 eligible quotes, event fee is USD 0.01005 before separate account costs and taxes. See `PRICING.md` and the official Pricing tab.

### Privacy, scope and boundaries

Input is owned/authorized by the customer. Uploads stay within **Apify infrastructure**, which hosts all inputs, results and run history under platform retention and access controls; do not treat this as end-to-end private storage. This Actor makes **no** external calls to supplier sites, Shopify, currency APIs, LLMs, analytics services, emails or proxies. Its output excludes raw uploaded files and buyer contact information. Strip names, postal addresses, emails, bank accounts and secrets before submitting: unlisted fields are rejected.

The Actor is not a purchase recommendation, tax advisor, inventory projection or RFQ comparator. It does not know stock available, destination-dependent freight/tariff changes, recoverability of tax, or future demand. Revalidate expiry, quantities, product specifications, and final amounts with the supplier.

Max 1,000 products, 1,000 quotes, 100 tiers/quote and 2 MB/file; results capped at 500 per run. All cost inputs must match actual **rounded** order quantity. Price tiers mean **all-units** tiers; if your contract uses incremental volume pricing, calculate and supply the applicable price externally.

### Local check

```bash
npm ci
npm test
## Set a local INPUT.json in your Apify storage directory, then:
npm start
```

See `SECURITY.md` (data policy), `COMPETITORS.md` (Store differentiation) and `PRICING.md` (fees). API19 is independent from API10 and every other Apify project.

# Actor input Schema

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

Fields: sku, salePrice (excludes sales tax), currency, saleUnit, optional targetMarginPct. Do not include buyer contact data.

## `productsCsv` (type: `string`):

Alternative to products array. Header: sku,salePrice,currency,saleUnit,targetMarginPct.

## `productsFile` (type: `string`):

Alternative to products / productsCsv. The uploaded file must be a readable Apify Key-Value Store record; third-party URLs are rejected.

## `quotes` (type: `array`):

Each: quoteId,sku,supplier,currency,purchaseUnit,moq,packSize,tiers\[{minQty,unitPrice}],validUntil,costs{freightTotal,nonRecoverableTaxTotal or nonRecoverableTaxRatePct,otherLandedTotal,salesCommissionPct}. If units differ, state unitsPerPurchaseUnit.

## `quotesCsv` (type: `string`):

Alternative to quotes array. Columns include unitPrice for a single all-units tier or tiersJson for multiple tiers, plus flat fee columns.

## `quotesFile` (type: `string`):

Alternative to quotes / quotesCsv. Sensitive uploaded files are read only within Apify storage; no arbitrary download URLs.

## `requestedQuantity` (type: `integer`):

Required SKU sales-unit quantity; procurement order is rounded up to MOQ and pack multiple.

## `targetMarginPct` (type: `number`):

Global target; individual products may override. Gross margin excludes sales commissions (shown separately).

## `targetCurrency` (type: `string`):

ISO 4217 uppercase 3-letter currency. Missing FX means incomparable.

## `fxRates` (type: `object`):

Optional: quote/sale currency -> number of reporting currency units per ONE foreign currency unit. E.g. EUR:1.1, no live FX.

## `asOf` (type: `string`):

YYYY-MM-DD; omitted means current UTC date.

## `maxResults` (type: `integer`):

Input order preserved. No source data leaves Apify.

## Actor input object example

```json
{
  "requestedQuantity": 20,
  "targetMarginPct": 35,
  "targetCurrency": "USD",
  "fxRates": {},
  "maxResults": 100
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("afkdigital/restock-margin-guard").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("afkdigital/restock-margin-guard").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 '{}' |
apify call afkdigital/restock-margin-guard --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,afkdigital/restock-margin-guard"
        }
    }
}
```

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/t7awXvs0YaNL80rrW/builds/QiPgzAeZc1kBGtZsO/openapi.json
