# Tarazu -Shopify Free Shipping & Discount Tester (`sayuj/tarazu`) Actor

Test your Shopify store's free-shipping threshold, discount codes, automatic discounts and cart totals over the public Ajax Cart API. Boundary carts, exact money math and evidence for every verdict. Never opens checkout or places orders.

- **URL**: https://apify.com/sayuj/tarazu.md
- **Developed by:** [Sayuj Pillai](https://apify.com/sayuj) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $30.00 / 1,000 scenarios

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

![Tarazu — Shopify Free Shipping & Discount Tester](https://api.apify.com/v2/key-value-stores/Rb5YdEZ3JbsTQUcig/records/tarazu-banner.png)

### What is Tarazu?

**Tarazu** (Hindi/Urdu for *balance scale*) is a **Shopify free shipping and discount tester**. It checks that your
store's **free-shipping threshold, discount codes, automatic discounts and cart totals** behave the way you intended.
It builds real carts through the storefront's public Ajax Cart API, places them just below, exactly at and just above
your threshold, applies your codes, reads the real cart and shipping quotes, and gives every rule a verdict:
`pass`, `fail`, `inconclusive` or `unsupported`, each with evidence.

Tarazu **never opens checkout and never places an order**. It only uses cart endpoints, on stores you own or are
authorized to test.

### Who is it for?

- **Shopify agencies and theme developers** who want a quick QA pass after a theme, app or discount change.
- **Merchants** who want a check before a sale or campaign goes live, or a daily scheduled run.
- Anyone who has asked *"does free shipping still apply after someone uses a discount code?"*

### What does it check?

| Check | What Tarazu verifies |
|---|---|
| Free-shipping threshold | Carts just below, at and above your threshold get (or don't get) a free rate. Tested before or after discounts, with `>=` or `>`. |
| Discount codes | The code applies (or doesn't), and takes off exactly the amount you expect. |
| Automatic discounts | An automatic discount is present and takes off the expected amount. |
| Cart totals | The total, subtotal or discount equals a number, or a multi-step formula such as "10% then ₹500 off". |
| Line prices | A variant's price in the cart is what you expect. |
| Variant availability | A sold-out variant really can't be added, and an in-stock one can. |
| Currency | The cart is in the currency of the market you pinned. If the currency is wrong, money checks are marked inconclusive instead of being compared across currencies. |
| Cart page total | The `/cart` page shows the same total as the cart API (depends on the theme). |

Checkout-only behaviour (cash on delivery, payment methods, tax, final checkout total, shipping-discount codes) is
reported as **unsupported**, never guessed.

### How to use it

1. **Try the demo first.** Leave **Mode** on *Demo store* and click **Start**. Tarazu runs 11 scenarios against two
   built-in stores (one configured correctly, one with deliberate bugs) and shows every verdict type. Demo runs are free.
2. **Switch Mode to *My store*** and enter your **Store URL** (for example `https://your-store.myshopify.com`).
3. Tick **I own this store or am authorized to test it**.
4. For a password-protected development or staging store, enter the **Storefront password** (Shopify admin → Online
   Store → Preferences). It is stored as a secret and never written to results or logs.
5. Set **Market country**, **Expected cart currency** and a **shipping address** (country, province, postal code),
   which shipping quotes need.
6. Add what you want to test:
   - **Free-shipping threshold** (for example `4000`) plus 2–5 **Variants for boundary carts**. Tarazu works out the
     carts closest to the threshold itself.
   - **Discount code checks**, for example `FLAT500` on 4 posters, expecting ₹500.00 off.
   - **Scenarios** for anything else (JSON, see the example below).

**Where do I find a variant ID?** In Shopify admin open Products → your product → click the variant. The number at the
end of the URL is the variant ID. You can also open `https://your-store/products/<handle>.js` and read `variants[].id`.

### Input example

```json
{
  "mode": "live",
  "storeUrl": "https://your-store.myshopify.com",
  "authorizationConfirmed": true,
  "storefrontPassword": "••••••",
  "country": "IN",
  "expectedCurrency": "INR",
  "shippingCountry": "IN",
  "shippingProvince": "MH",
  "shippingZip": "400001",
  "freeShippingThreshold": "4000",
  "boundaryVariantIds": ["44847419785275", "44847420768315", "44847421521979"],
  "discountChecks": [
    { "code": "FLAT500", "lines": [{ "variantId": "44847419785275", "quantity": 4 }], "expectedAmount": "500.00" }
  ],
  "scenarios": [
    {
      "id": "flat500-free-shipping",
      "lines": [{ "variantId": "44847419785275", "quantity": 4 }],
      "discountCodes": ["FLAT500"],
      "checks": [{ "type": "free_shipping", "threshold": "4000", "basis": "after_discounts" }]
    }
  ]
}
```

### Output example

One dataset row per check. This row is from a real run against a test store:

```json
{
  "scenarioId": "boundary-below",
  "scenarioLabel": "List-price boundary: ₹3,999.99, just below the ₹4,000.00 threshold",
  "checkType": "free_shipping",
  "title": "Free shipping at ₹4,000.00 (after discounts, inclusive)",
  "verdict": "pass",
  "reasonCode": "FREE_SHIPPING_WITHHELD_AS_EXPECTED",
  "message": "The cart total after discounts (total_price) is ₹3,999.99, which is below the ₹4,000.00 threshold, and no free rate was offered (cheapest: \"Standard\" at ₹99.00).",
  "expected": "no free shipping (total_price ₹3,999.99 < ₹4,000.00)",
  "actual": "no free shipping (cheapest \"Standard\" at ₹99.00)",
  "amounts": { "currency": "INR", "originalTotalMinor": 399999, "totalMinor": 399999, "totalDiscountMinor": 0 },
  "shippingRates": [{ "name": "Standard", "priceMinor": 9900, "currency": "INR" }]
}
```

In the dataset these fields sit inside `data`, next to `resultId`, `status`, `observedAt` and `sourceRefs` (the exact
Shopify requests used as evidence). Each run also stores:

- **REPORT**: a readable HTML QA report you can share with a client.
- **SUMMARY**: JSON totals, per-scenario verdicts and why the run stopped.
- **EVIDENCE-<scenario>**: the full cart, shipping quotes and request log for each scenario.

### What the verdicts mean

- **pass**: the store behaves as you specified.
- **fail**: it doesn't. The message says what Tarazu expected and what it saw.
- **inconclusive**: Tarazu couldn't observe enough to decide (for example the shipping quote was unavailable or the
  currency was wrong). It never reports a pass without evidence.
- **unsupported**: the rule can only be seen at checkout, which Tarazu deliberately never opens.

### How much does it cost?

Tarazu charges one **`scenario-tested`** event per live scenario that was actually tested, meaning the cart was read
and at least one check reached pass or fail. A typical suite (3 boundary carts, 2 code checks, 2 extra scenarios)
is 7 events. Demo runs, failed store connections and checkout-only (`unsupported`) scenarios are not charged.
See the **Pricing** tab for the current price per event.

### Safety

- Only Shopify cart endpoints are allowed (`/cart.js`, `/cart/add.js`, `/cart/update.js`, shipping rates and a few
  others). Checkout, orders, accounts and admin URLs are blocked in code before any request is sent.
- Private and internal network addresses are refused, including through redirects.
- The storefront password and Crawler access signatures are secrets. Cart tokens are shortened in evidence.
- Requests are paced (1 s apart by default), and every scenario uses its own fresh cart.

### Limitations

- Shipping-discount codes and free-shipping rules that only run at checkout don't show up in cart shipping quotes.
- A customer-segment discount code can look applied in a guest cart and still be removed at checkout.
- Shopify's bot protection can rate-limit automated carts. If you see `BOT_CHALLENGE`, create a signature under
  Online Store → Preferences → Crawler access and paste both values into the input.
- The cart page total check depends on your theme; headless storefronts report it as inconclusive.

### FAQ

**Does it create orders or touch checkout?** No. It only builds carts, and the test carts simply expire.

**Can I test my client's store?** Only with the owner's permission. You must tick the authorization box for live runs.

**Does free shipping use the total before or after discounts?** On the stores we tested, Shopify used the total
**after** discounts: 4 × ₹1,000 with `FLAT500` (₹3,500) did not get free shipping at a ₹4,000 threshold. Tarazu
lets you assert whichever rule you intend.

**Can I schedule it?** Yes. Save your input as a Task and add a schedule; failed checks show up as `fail` rows and
in the SUMMARY record.

# Changelog

This Actor's version history is a separate document: https://apify.com/sayuj/tarazu/changelog.md

# Actor input Schema

## `mode` (type: `string`):

<strong>Demo store</strong> runs a built-in test suite against a simulated Shopify store: free, no store needed, and every store field below is ignored. <strong>My store</strong> tests the store in Store URL, which you must own or be authorized to test.

## `storeUrl` (type: `string`):

Your storefront address with <code>https://</code> and nothing after the domain, e.g. <code>https://your-store.myshopify.com</code> or <code>https://www.yourbrand.com</code>. Shopify admin → <strong>Settings → Domains</strong> lists your domains. Put a market subfolder such as <code>/en-in</code> in Locale path instead.

## `authorizationConfirmed` (type: `boolean`):

Required in live mode. Tarazu adds products to test carts on the store you name, so only run it on stores you own or have permission to test, for example a client store your agency maintains. Ticking the box is your declaration; Tarazu cannot verify ownership.

## `storefrontPassword` (type: `string`):

Only for password-protected stores (development, staging or pre-launch). Shopify admin → <strong>Online Store → Preferences → Password protection</strong> shows it. Stored encrypted and never written to results or logs.

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

Two-letter country code (ISO 3166-1, capitals) of the market to test, e.g. <code>IN</code>. Tarazu selects this market through Shopify's localization form before building carts, so prices and currency match it. Leave empty to use the store's default market.

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

Three-letter currency code (ISO 4217, capitals), e.g. <code>INR</code>. Adds a currency check to every scenario. If the cart uses another currency, that scenario's money checks are reported as inconclusive instead of being compared across currencies.

## `localePath` (type: `string`):

Market subfolder put in front of every request, e.g. <code>/en-in</code> when your India market lives at <code>yourbrand.com/en-in</code>. Leave empty for the primary market. Shopify admin → <strong>Settings → Markets</strong> shows each market's domain or subfolder.

## `shippingCountry` (type: `string`):

Two-letter country code of the address Tarazu requests shipping rates for, e.g. <code>IN</code>. Defaults to Market country. Required for free-shipping tests.

## `shippingProvince` (type: `string`):

Province or state of the shipping address as a code or name, e.g. <code>MH</code> or <code>Maharashtra</code>. Shopify needs it for shipping rates in many countries, including India, the USA and Canada.

## `shippingZip` (type: `string`):

Postal or ZIP code of the shipping address, e.g. <code>400001</code>. Use an address inside the shipping zone whose rates you want to test.

## `freeShippingThreshold` (type: `string`):

Order amount from which shipping should be free, in the store currency's major units with a dot for decimals, e.g. <code>4000</code> or <code>4000.00</code> (no currency symbol, no thousands separators). Shopify admin → <strong>Settings → Shipping and delivery</strong> → your free rate's price condition shows it.

## `freeShippingBasis` (type: `string`):

Which amount should count toward the threshold: the cart total <strong>after</strong> discounts, or the subtotal <strong>before</strong> discounts.

## `freeShippingInclusive` (type: `boolean`):

On: a cart of exactly the threshold (e.g. 4000.00) should ship free (amount ≥ threshold). Off: only carts above it ship free (amount > threshold).

## `freeShippingRateName` (type: `string`):

Optional. When set, only a rate with exactly this name and price 0 counts as free shipping, e.g. <code>Free Shipping</code>. When empty, any rate priced 0 counts.

## `boundaryVariantIds` (type: `array`):

Up to 10 variant IDs Tarazu may combine into the boundary carts; a mix of prices makes exact boundaries easier to hit. To find a variant ID, open Shopify admin → <strong>Products</strong>, choose the product and click the variant: the number at the end of the page URL is the variant ID.

## `boundaryDiscountCodes` (type: `array`):

Optional, up to 5 discount codes applied to every boundary cart, e.g. to check that a code which takes the cart below the threshold also removes free shipping. One code per entry; codes are case-insensitive.

## `discountChecks` (type: `array`):

One test cart per entry, each with one discount code: <code>code</code>, <code>lines</code> (variant IDs and quantities), optional <code>expectApplicable</code> (default true) and optional <code>expectedAmount</code> (the discount in major units). Example: <code>\[{"code": "FLAT500", "lines": \[{"variantId": "41234567890123", "quantity": 2}], "expectedAmount": "500.00"}]</code>. A code the cart accepts can still be rejected at checkout if it is limited to certain customers.

## `scenarios` (type: `array`):

Explicit test carts. Each scenario has an <code>id</code> (lowercase letters, digits, - or \_), an optional <code>label</code>, <code>lines</code> (1 to 20 of <code>{"variantId": "…", "quantity": 1}</code>), optional <code>discountCodes</code> (up to 5) and <code>checks</code> (1 to 20). Check types: <code>free_shipping</code> (threshold, inclusive, basis, rateName), <code>discount_code</code> (code, expectApplicable, expectedAmount), <code>automatic_discount</code> (title, expectPresent, expectedAmount), <code>cart_total</code> (field plus expected or computeFrom), <code>line_price</code> (variantId, expectedUnitPrice, field), <code>variant_available</code> (variantId, expectAvailable), <code>currency</code> (expected) and <code>cart_page_total</code> (selector). Checkout-only types (<code>cod_available</code>, <code>payment_method</code>, <code>tax_amount</code>, <code>checkout_total</code>, <code>shipping_discount</code>) are accepted and always reported as unsupported.

## `maxScenarios` (type: `integer`):

Upper limit of test carts per run (boundary carts, discount checks and your scenarios together), 1 to 20. Input that could create more scenarios than this is rejected before any request is sent.

## `maxRuntimeSeconds` (type: `integer`):

Run time budget, 30 to 900 seconds. Tarazu stops starting new scenarios when 20 seconds or less remain and reports which scenarios were not covered.

## `requestDelayMs` (type: `integer`):

Pause between requests to your store in live mode, 0 to 10000 milliseconds, so Shopify's bot protection does not throttle the test. The demo store uses no delay.

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

Usually not needed for your own store; a crawler-access signature (below) is the better way past Shopify's bot protection. Apify Proxy traffic is charged by Apify.

## `crawlerSignatureInput` (type: `string`):

Optional, recommended for live tests. In Shopify admin open <strong>Online Store → Preferences → Crawler access → Create signature</strong>, copy the <code>Signature-Input</code> value here and the <code>Signature</code> value into the next field. Signed requests identify Tarazu to Shopify's bot protection and avoid 429 throttling. A signature expires after at most 3 months.

## `crawlerSignature` (type: `string`):

The <code>Signature</code> value of the same crawler-access signature. Both values are sent with <code>Signature-Agent: "https://shopify.com"</code>. Stored encrypted and never written to results or logs.

## Actor input object example

```json
{
  "mode": "demo",
  "storeUrl": "https://your-store.myshopify.com",
  "authorizationConfirmed": false,
  "country": "IN",
  "expectedCurrency": "INR",
  "localePath": "/en-in",
  "shippingCountry": "IN",
  "shippingProvince": "MH",
  "shippingZip": "400001",
  "freeShippingThreshold": "4000.00",
  "freeShippingBasis": "after_discounts",
  "freeShippingInclusive": true,
  "freeShippingRateName": "Free Shipping",
  "boundaryVariantIds": [
    "41234567890123",
    "41234567890456"
  ],
  "boundaryDiscountCodes": [
    "FLAT500"
  ],
  "discountChecks": [
    {
      "code": "FLAT500",
      "lines": [
        {
          "variantId": "41234567890123",
          "quantity": 2
        }
      ],
      "expectApplicable": true,
      "expectedAmount": "500.00"
    }
  ],
  "scenarios": [
    {
      "id": "mug-monday",
      "label": "Mug Monday takes 10% off the mug",
      "lines": [
        {
          "variantId": "41234567890789",
          "quantity": 1
        }
      ],
      "checks": [
        {
          "type": "automatic_discount",
          "title": "Mug Monday",
          "expectedAmount": "49.90"
        },
        {
          "type": "cart_total",
          "field": "total_price",
          "computeFrom": {
            "base": "original_total_price",
            "steps": [
              {
                "kind": "percentage",
                "value": "10"
              }
            ],
            "rounding": "half_up"
          }
        }
      ]
    }
  ],
  "maxScenarios": 20,
  "maxRuntimeSeconds": 240,
  "requestDelayMs": 1000,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `checks` (type: `string`):

One row per check with verdict, expected versus actual and the reason.

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

JSON totals: scenarios covered, verdict counts, billing, network statistics and why the run stopped.

## `report` (type: `string`):

Self-contained HTML report you can share with your team or client.

## `evidence` (type: `string`):

One redacted record per scenario with the cart snapshot, shipping rates and request log behind every verdict.

# 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 = {
    "mode": "demo"
};

// Run the Actor and wait for it to finish
const run = await client.actor("sayuj/tarazu").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 = { "mode": "demo" }

# Run the Actor and wait for it to finish
run = client.actor("sayuj/tarazu").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 '{
  "mode": "demo"
}' |
apify call sayuj/tarazu --silent --output-dataset

```

## MCP server setup

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

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/Y5tcZTA76Ti93189v/builds/BHARm1htTcu8kzjLM/openapi.json
