Repair Inventory and catalog

Negative inventory from overselling

A SKU shows -3 in the admin instead of 0. Nobody typed that number in. It got there because checkout let two orders take the last unit at almost the same moment, or an import wrote a bad delta, or a channel sync double counted a sale. Whatever the cause, BigCommerce keeps selling that SKU exactly like it has real stock, because nothing in checkout stops the count from going below zero. Here is why that happens and a small script that finds every negative SKU and safely resets it back to zero.

Python and Node.js V3 Catalog and Inventory REST Safe by default (dry run)
The short answer

BigCommerce tracks stock on a variant with inventory_level, and that number has no built-in floor at zero. When checkout, an import, or a sync writes a decrement that pushes a tracked variant below zero, the SKU sells on exactly as before. Run a small Python or Node.js script that scans every product's variants, keeps only the ones where the product genuinely tracks inventory at the variant level and inventory_level is below zero, and repairs each one by posting an absolute adjustment back to zero with POST /v3/inventory/adjustments/absolute, while keeping the oversold quantity for a restock log. Full code, tests, and a dry run guard are below.

The problem in plain words

In BigCommerce, a variant's inventory_level is just an integer field. Checkout decrements it when an order is placed, the way you would expect. What most people do not expect is that nothing on the platform refuses to let that number go below zero. There is no floor, no automatic clamp, no rejected order once stock hits zero unless you have separately configured out-of-stock behavior to hide the product.

So the count keeps falling with every additional sale. A SKU that should have stopped at zero reads -1, then -5, then -20, and checkout treats all of those the same as a healthy stock number, because it is just reading an integer, not asking whether the integer makes sense.

SKU has 1 left inventory_level = 1 Two orders decrement it almost the same moment no floor at zero inventory_level = -1 Checkout keeps selling it
Nothing in checkout refuses a negative count, so a SKU that sold past zero looks and behaves the same as one with real stock.

Why it happens

The root cause is that BigCommerce treats inventory_level as a plain integer with no built-in floor, so anything that writes a decrement without checking the current count first can push it negative. A few common ways stores end up here:

This is a common source of confusion because the number in the admin looks like any other integer. Nobody notices a negative SKU until a report or a support ticket points it out, and by then it may have kept selling for days. See the citations at the end for the exact support articles and community threads.

The key insight

Negative inventory is a symptom, not a cause. Resetting a SKU's count to zero does not undo the orders that already shipped against stock that was not really there, and it does not fix the race condition or the bad import that caused it. So the safe pattern is to treat the reset as a floor correction, keep the oversold quantity as data for a restock decision, and only ever touch a variant whose product actually tracks inventory at the variant level, since a negative number on an untracked product is not a real stock signal at all.

The fix, as a flow

We do not touch which orders shipped. We add a job that scans every product's variants, decides which ones are genuinely oversold on a tracked SKU, and for each one posts one absolute adjustment that floors the count back to zero while recording how many units sold past zero. Everything else, including negative numbers on products that do not track variant-level stock, is left untouched.

Scan products include=variants Read each variant inventory_level, tracking Tracked and negative? no, skip yes Log oversold qty for restock review POST adjustments absolute, quantity=0
Only a tracked variant with a genuinely negative count gets an adjustment. The oversold quantity is logged before the reset, not thrown away.

Build it step by step

1

Get a store hash and an API account token

In your BigCommerce control panel, go to Settings, API, Store-level API accounts, and create an account with the Products scope set to modify. Note the store hash from your control panel URL and the access token from the API account. Keep both in environment variables, never in the file.

setup (shell)
pip install requests

export BIGCOMMERCE_STORE_HASH="abc123"
export BIGCOMMERCE_ACCESS_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export ADJUSTMENT_REASON="negative_inventory_overselling_repair"
export DRY_RUN="true"   # start safe, change to false to write
setup (shell)
// Node 18+ has fetch built in, no dependencies needed

export BIGCOMMERCE_STORE_HASH="abc123"
export BIGCOMMERCE_ACCESS_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export ADJUSTMENT_REASON="negative_inventory_overselling_repair"
export DRY_RUN="true"   // start safe, change to false to write
2

Talk to the BigCommerce REST API

Every call sends your token in the X-Auth-Token header against https://api.bigcommerce.com/stores/{store_hash}/. A small helper wraps GET and POST, raises on a non-2xx response, and unwraps the V3 {"data": ...} envelope so callers get plain objects back.

step2.py
import os, requests

STORE_HASH = os.environ["BIGCOMMERCE_STORE_HASH"]
TOKEN = os.environ["BIGCOMMERCE_ACCESS_TOKEN"]
BASE = f"https://api.bigcommerce.com/stores/{STORE_HASH}/"

def bc(method, path, **kwargs):
    r = requests.request(
        method, BASE + path.lstrip("/"),
        headers={"X-Auth-Token": TOKEN, "Content-Type": "application/json", "Accept": "application/json"},
        timeout=30, **kwargs,
    )
    r.raise_for_status()
    if not r.content:
        return None
    body = r.json()
    return body["data"] if isinstance(body, dict) and "data" in body else body
step2.js
const STORE_HASH = process.env.BIGCOMMERCE_STORE_HASH;
const TOKEN = process.env.BIGCOMMERCE_ACCESS_TOKEN;
const BASE = `https://api.bigcommerce.com/stores/${STORE_HASH}/`;

async function bc(method, path, body) {
  const res = await fetch(BASE + path.replace(/^\//, ""), {
    method,
    headers: { "X-Auth-Token": TOKEN, "Content-Type": "application/json", "Accept": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) throw new Error(`BigCommerce ${res.status}`);
  const text = await res.text();
  if (!text) return null;
  const json = JSON.parse(text);
  return json && typeof json === "object" && "data" in json ? json.data : json;
}
3

Scan every product with its variants

Call GET /v3/catalog/products?include=variants&limit=250 and page through the whole catalog. Each product carries its own inventory_tracking, and each variant carries its own id, sku, and inventory_level, which is exactly what the decision needs next. This step never writes anything, it just reads.

step3.py
def all_products():
    page = 1
    limit = 250
    while True:
        batch = bc("GET", f"/v3/catalog/products?include=variants&limit={limit}&page={page}")
        if not batch:
            return
        for product in batch:
            yield product
        if len(batch) < limit:
            return
        page += 1
step3.js
async function* allProducts() {
  let page = 1;
  const limit = 250;
  while (true) {
    const batch = await bc("GET", `/v3/catalog/products?include=variants&limit=${limit}&page=${page}`);
    if (!batch || !batch.length) return;
    for (const product of batch) yield product;
    if (batch.length < limit) return;
    page += 1;
  }
}
4

Classify, with one pure function

Keep the decision in its own function that takes a product and one of its variants and returns whether it needs a fix and by how much. A negative number on a product that does not track inventory at the variant level is not a real stock signal, so it is left alone. A variant already at zero or above needs nothing. Only a tracked variant with inventory_level below zero is genuinely oversold, and the function returns exactly how many units sold past zero.

decide.py
def classify_negative_inventory(product, variant):
    if product.get("inventory_tracking") != "variant":
        return {
            "productId": product["id"], "variantId": variant["id"], "sku": variant.get("sku"),
            "needsFix": False, "oversoldBy": 0,
        }

    level = variant.get("inventory_level", 0)
    if level >= 0:
        return {
            "productId": product["id"], "variantId": variant["id"], "sku": variant.get("sku"),
            "needsFix": False, "oversoldBy": 0,
        }

    return {
        "productId": product["id"], "variantId": variant["id"], "sku": variant.get("sku"),
        "needsFix": True, "oversoldBy": abs(level),
    }
decide.js
export function classifyNegativeInventory(product, variant) {
  if (product.inventory_tracking !== "variant") {
    return { productId: product.id, variantId: variant.id, sku: variant.sku, needsFix: false, oversoldBy: 0 };
  }

  const level = variant.inventory_level ?? 0;
  if (level >= 0) {
    return { productId: product.id, variantId: variant.id, sku: variant.sku, needsFix: false, oversoldBy: 0 };
  }

  return { productId: product.id, variantId: variant.id, sku: variant.sku, needsFix: true, oversoldBy: Math.abs(level) };
}
5

Reset the count with an absolute adjustment

When a variant is genuinely oversold, call POST /v3/inventory/adjustments/absolute with the SKU and a target quantity of zero. An absolute adjustment sets the count directly rather than adding or subtracting, so it floors the SKU back to zero regardless of how negative it had gone. Pass a reason so the adjustment shows up clearly in BigCommerce's own inventory history.

apply.py
def reset_variant_to_zero(sku, reason):
    payload = {"reason": reason, "items": [{"sku": sku, "quantity": 0}]}
    return bc("POST", "/v3/inventory/adjustments/absolute", json=payload)
apply.js
async function resetVariantToZero(sku, reason) {
  const payload = { reason, items: [{ sku, quantity: 0 }] };
  return bc("POST", "/v3/inventory/adjustments/absolute", payload);
}
6

Wire it together with a dry run guard

The loop ties every piece together. On the first few runs, leave DRY_RUN on so the script only logs which SKUs it would reset and by how much. That log is your restock and demand planning list, since every unit it reports was sold without real stock behind it. Run the job on a schedule, for example every hour or after each high-traffic sale window.

Run it safe

Always start with DRY_RUN=true. The script never touches an untracked product's inventory field and never resets a SKU that is already at zero or above, so it is safe to run again and again. Keep the logged oversold quantities, they are the real signal for what to restock and how demand planning missed it.

The full code

Here is the complete script in one file for each language. It reads settings from the environment, logs what it does, respects the dry run flag, and is safe to run again and again because it only resets a SKU that is genuinely tracked at the variant level and currently negative.

View this code on GitHub Full runnable folder with tests in the bigcommerce-fixes repo.

fix_negative_inventory.py
"""Find and safely repair BigCommerce variants that oversold into negative inventory.

inventory_level on a variant is meant to floor at zero. But when two checkouts
decrement the same low-stock SKU at nearly the same moment, or a bulk import
writes a negative delta, or a channel sync double counts a sale, the number can
end up below zero. BigCommerce keeps selling a SKU that reads -3 exactly the same
as one that reads 30, because nothing in checkout refuses a negative count.

This scans every product with GET /v3/catalog/products?include=variants, finds
variants whose inventory_level is below zero, and classifies each one with a pure
function. A negative count on a product with inventory_tracking off is not really
a stock problem and is left alone. A negative count on a tracked variant is
repaired by posting an absolute adjustment back to zero with
POST /v3/inventory/adjustments/absolute, and the lost quantity is kept in the
result so it can be logged for restock and demand planning. Guarded by DRY_RUN.
Safe to run again and again, since a variant already at zero or above is skipped.
"""
import os
import logging

import requests

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("fix_negative_inventory")

STORE_HASH = os.environ["BIGCOMMERCE_STORE_HASH"]
TOKEN = os.environ["BIGCOMMERCE_ACCESS_TOKEN"]
BASE = f"https://api.bigcommerce.com/stores/{STORE_HASH}/"
DRY_RUN = os.environ.get("DRY_RUN", "true").lower() == "true"
ADJUSTMENT_REASON = os.environ.get("ADJUSTMENT_REASON", "negative_inventory_overselling_repair")


def bc(method, path, **kwargs):
    r = requests.request(
        method, BASE + path.lstrip("/"),
        headers={"X-Auth-Token": TOKEN, "Content-Type": "application/json", "Accept": "application/json"},
        timeout=30, **kwargs,
    )
    r.raise_for_status()
    if not r.content:
        return None
    body = r.json()
    return body["data"] if isinstance(body, dict) and "data" in body else body


def classify_negative_inventory(product, variant):
    """Pure classification. No network calls.

    product: {"id": int, "inventory_tracking": "none" | "product" | "variant"}
    variant: {"id": int, "sku": str, "inventory_level": int}

    Returns {"productId", "variantId", "sku", "needsFix", "oversoldBy"}.

    1. If the product does not track inventory at the variant level, a negative
       number on that variant is cosmetic, not a real oversell, so it is left alone.
    2. If inventory_level is zero or positive, there is nothing to repair.
    3. Otherwise the variant is genuinely oversold. oversoldBy is the positive
       quantity that sold past zero, kept so the caller can log it for restock
       and demand planning before the count is corrected back to zero.
    """
    if product.get("inventory_tracking") != "variant":
        return {
            "productId": product["id"], "variantId": variant["id"], "sku": variant.get("sku"),
            "needsFix": False, "oversoldBy": 0,
        }

    level = variant.get("inventory_level", 0)
    if level >= 0:
        return {
            "productId": product["id"], "variantId": variant["id"], "sku": variant.get("sku"),
            "needsFix": False, "oversoldBy": 0,
        }

    return {
        "productId": product["id"], "variantId": variant["id"], "sku": variant.get("sku"),
        "needsFix": True, "oversoldBy": abs(level),
    }


def all_products():
    """Yield every product with its variants, paginated."""
    page = 1
    limit = 250
    while True:
        batch = bc("GET", f"/v3/catalog/products?include=variants&limit={limit}&page={page}")
        if not batch:
            return
        for product in batch:
            yield product
        if len(batch) < limit:
            return
        page += 1


def reset_variant_to_zero(sku, reason):
    """Post an absolute inventory adjustment that floors a SKU back to zero."""
    payload = {"reason": reason, "items": [{"sku": sku, "quantity": 0}]}
    return bc("POST", "/v3/inventory/adjustments/absolute", json=payload)


def run():
    repaired = 0
    total_oversold = 0
    for product in all_products():
        for variant in product.get("variants") or []:
            decision = classify_negative_inventory(product, variant)
            if not decision["needsFix"]:
                continue

            log.warning(
                "SKU %s (variant %s) oversold by %d units. %s",
                decision["sku"], decision["variantId"], decision["oversoldBy"],
                "would reset to 0" if DRY_RUN else "resetting to 0",
            )
            if not DRY_RUN:
                reset_variant_to_zero(decision["sku"], ADJUSTMENT_REASON)
            repaired += 1
            total_oversold += decision["oversoldBy"]

    log.info(
        "Done. %d variant(s) %s, %d unit(s) oversold in total.",
        repaired, "to reset" if DRY_RUN else "reset to 0", total_oversold,
    )


if __name__ == "__main__":
    run()
fix-negative-inventory.js
/**
 * Find and safely repair BigCommerce variants that oversold into negative inventory.
 *
 * inventory_level on a variant is meant to floor at zero. But when two checkouts
 * decrement the same low-stock SKU at nearly the same moment, or a bulk import
 * writes a negative delta, or a channel sync double counts a sale, the number can
 * end up below zero. BigCommerce keeps selling a SKU that reads -3 exactly the same
 * as one that reads 30, because nothing in checkout refuses a negative count.
 *
 * This scans every product with GET /v3/catalog/products?include=variants, finds
 * variants whose inventory_level is below zero, and classifies each one with a pure
 * function. A negative count on a product with inventory_tracking off is not really
 * a stock problem and is left alone. A negative count on a tracked variant is
 * repaired by posting an absolute adjustment back to zero with
 * POST /v3/inventory/adjustments/absolute, and the lost quantity is kept in the
 * result so it can be logged for restock and demand planning. Guarded by DRY_RUN.
 * Safe to run again and again, since a variant already at zero or above is skipped.
 *
 * Guide: https://www.allanninal.dev/bigcommerce/negative-inventory-from-overselling/
 */
import { pathToFileURL } from "node:url";

const STORE_HASH = process.env.BIGCOMMERCE_STORE_HASH || "example-store-hash";
const TOKEN = process.env.BIGCOMMERCE_ACCESS_TOKEN || "dummy-token";
const BASE = `https://api.bigcommerce.com/stores/${STORE_HASH}/`;
const DRY_RUN = (process.env.DRY_RUN || "true").toLowerCase() === "true";
const ADJUSTMENT_REASON = process.env.ADJUSTMENT_REASON || "negative_inventory_overselling_repair";

/**
 * Pure classification. No network calls.
 *
 * product: { id, inventory_tracking: "none" | "product" | "variant" }
 * variant: { id, sku, inventory_level }
 *
 * Returns { productId, variantId, sku, needsFix, oversoldBy }.
 *
 * 1. If the product does not track inventory at the variant level, a negative
 *    number on that variant is cosmetic, not a real oversell, so it is left alone.
 * 2. If inventory_level is zero or positive, there is nothing to repair.
 * 3. Otherwise the variant is genuinely oversold. oversoldBy is the positive
 *    quantity that sold past zero, kept so the caller can log it for restock
 *    and demand planning before the count is corrected back to zero.
 */
export function classifyNegativeInventory(product, variant) {
  if (product.inventory_tracking !== "variant") {
    return { productId: product.id, variantId: variant.id, sku: variant.sku, needsFix: false, oversoldBy: 0 };
  }

  const level = variant.inventory_level ?? 0;
  if (level >= 0) {
    return { productId: product.id, variantId: variant.id, sku: variant.sku, needsFix: false, oversoldBy: 0 };
  }

  return { productId: product.id, variantId: variant.id, sku: variant.sku, needsFix: true, oversoldBy: Math.abs(level) };
}

async function bc(method, path, body) {
  const res = await fetch(BASE + path.replace(/^\//, ""), {
    method,
    headers: { "X-Auth-Token": TOKEN, "Content-Type": "application/json", "Accept": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) throw new Error(`BigCommerce ${res.status}`);
  const text = await res.text();
  if (!text) return null;
  const json = JSON.parse(text);
  return json && typeof json === "object" && "data" in json ? json.data : json;
}

async function* allProducts() {
  let page = 1;
  const limit = 250;
  while (true) {
    const batch = await bc("GET", `/v3/catalog/products?include=variants&limit=${limit}&page=${page}`);
    if (!batch || !batch.length) return;
    for (const product of batch) yield product;
    if (batch.length < limit) return;
    page += 1;
  }
}

async function resetVariantToZero(sku, reason) {
  const payload = { reason, items: [{ sku, quantity: 0 }] };
  return bc("POST", "/v3/inventory/adjustments/absolute", payload);
}

export async function run() {
  let repaired = 0;
  let totalOversold = 0;
  for await (const product of allProducts()) {
    for (const variant of product.variants || []) {
      const decision = classifyNegativeInventory(product, variant);
      if (!decision.needsFix) continue;

      console.warn(
        `SKU ${decision.sku} (variant ${decision.variantId}) oversold by ${decision.oversoldBy} units. ${DRY_RUN ? "would reset to 0" : "resetting to 0"}`
      );
      if (!DRY_RUN) await resetVariantToZero(decision.sku, ADJUSTMENT_REASON);
      repaired++;
      totalOversold += decision.oversoldBy;
    }
  }
  console.log(`Done. ${repaired} variant(s) ${DRY_RUN ? "to reset" : "reset to 0"}, ${totalOversold} unit(s) oversold in total.`);
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
  run().catch((err) => { console.error(err); process.exit(1); });
}

Add a test

The classification rule is the part most worth testing, because it decides which SKUs get their count rewritten. Because we kept classify_negative_inventory pure, the test needs no network and no BigCommerce store. It just feeds in plain product and variant objects and checks the answer.

test_negative_inventory_classification.py
from fix_negative_inventory import classify_negative_inventory


def product(**over):
    base = {"id": 701, "inventory_tracking": "variant"}
    base.update(over)
    return base


def variant(**over):
    base = {"id": 9001, "sku": "MUG-RED", "inventory_level": -3}
    base.update(over)
    return base


def test_needs_fix_when_variant_tracked_and_negative():
    result = classify_negative_inventory(product(), variant())
    assert result["needsFix"] is True
    assert result["oversoldBy"] == 3
    assert result["sku"] == "MUG-RED"
    assert result["productId"] == 701
    assert result["variantId"] == 9001


def test_no_fix_when_inventory_level_is_zero():
    result = classify_negative_inventory(product(), variant(inventory_level=0))
    assert result["needsFix"] is False
    assert result["oversoldBy"] == 0


def test_no_fix_when_inventory_level_is_positive():
    result = classify_negative_inventory(product(), variant(inventory_level=12))
    assert result["needsFix"] is False


def test_no_fix_when_tracking_is_none():
    result = classify_negative_inventory(product(inventory_tracking="none"), variant())
    assert result["needsFix"] is False
    assert result["oversoldBy"] == 0


def test_no_fix_when_tracking_is_product_level():
    result = classify_negative_inventory(product(inventory_tracking="product"), variant())
    assert result["needsFix"] is False


def test_oversold_by_matches_absolute_value_of_deep_negative():
    result = classify_negative_inventory(product(), variant(inventory_level=-41))
    assert result["needsFix"] is True
    assert result["oversoldBy"] == 41
fix-negative-inventory.test.js
import { test } from "node:test";
import assert from "node:assert/strict";
import { classifyNegativeInventory } from "./fix-negative-inventory.js";

const product = (over = {}) => ({ id: 701, inventory_tracking: "variant", ...over });
const variant = (over = {}) => ({ id: 9001, sku: "MUG-RED", inventory_level: -3, ...over });

test("needs fix when variant tracked and negative", () => {
  const result = classifyNegativeInventory(product(), variant());
  assert.equal(result.needsFix, true);
  assert.equal(result.oversoldBy, 3);
  assert.equal(result.sku, "MUG-RED");
  assert.equal(result.productId, 701);
  assert.equal(result.variantId, 9001);
});

test("no fix when inventory_level is zero", () => {
  const result = classifyNegativeInventory(product(), variant({ inventory_level: 0 }));
  assert.equal(result.needsFix, false);
  assert.equal(result.oversoldBy, 0);
});

test("no fix when inventory_level is positive", () => {
  const result = classifyNegativeInventory(product(), variant({ inventory_level: 12 }));
  assert.equal(result.needsFix, false);
});

test("no fix when tracking is none", () => {
  const result = classifyNegativeInventory(product({ inventory_tracking: "none" }), variant());
  assert.equal(result.needsFix, false);
  assert.equal(result.oversoldBy, 0);
});

test("no fix when tracking is product level", () => {
  const result = classifyNegativeInventory(product({ inventory_tracking: "product" }), variant());
  assert.equal(result.needsFix, false);
});

test("oversold by matches absolute value of deep negative", () => {
  const result = classifyNegativeInventory(product(), variant({ inventory_level: -41 }));
  assert.equal(result.needsFix, true);
  assert.equal(result.oversoldBy, 41);
});

Case studies

Flash sale

The last unit sold twice in the same second

A gift shop ran a timed flash sale on a single popular mug design with one unit left. Two customers on different devices hit checkout close enough together that both orders decremented inventory_level before either request saw the other's result. The SKU went to -1 and kept selling to three more buyers over the next hour before anyone noticed the admin showed a negative number.

The scan found the SKU, confirmed the product tracked inventory at the variant level, and reset it to zero with one adjustment. The four oversold units were logged, so the shop knew exactly how many orders needed a delay notice and a rush reorder instead of guessing.

Marketplace sync

A double-counted sale from a connected channel

A homeware seller connected a marketplace channel that pushed its own sale events back into BigCommerce to keep stock in sync. A retry after a timeout caused one sale to be counted twice, decrementing the same SKU an extra time and pushing three different products into negative numbers over a week.

Running the script in dry run first showed exactly which SKUs and by how much, which lined up with the retried webhook the seller later found in the marketplace's own logs. The reset ran cleanly, and the oversold log became the evidence that got the sync bug fixed on the marketplace side.

What good looks like

After this runs on a schedule, a negative SKU never sits unnoticed. Every tracked variant that sold past zero gets floored back to a real count with one safe adjustment, and the oversold quantity is captured instead of silently disappearing. Restock decisions are based on what actually happened, not on a number that quietly kept sliding downward.

FAQ

Why does a BigCommerce variant show negative inventory?

BigCommerce does not stop checkout from decrementing a variant's inventory_level below zero. When two orders decrement the same low-stock SKU at nearly the same moment, or an import or channel sync writes a negative delta, the count can go negative and checkout keeps treating that SKU as available exactly like one with real stock.

Is it safe to just reset a negative inventory_level to zero?

Yes, when the product actually tracks inventory at the variant level and the current inventory_level is below zero. Resetting to zero does not touch which orders shipped, it only stops the SKU from continuing to look available while it is really out of stock. The oversold quantity itself should still be logged for restock and demand planning, since zero is a floor, not a record of what happened.

How do I detect negative inventory without changing anything?

Call GET /v3/catalog/products?include=variants and look at each variant's inventory_level alongside the parent product's inventory_tracking. A variant on a product tracked at the variant level with inventory_level below zero is genuinely oversold. This is a read-only check, so you can run it safely before deciding whether to repair anything.

Related field notes

Citations

On the problem:

  1. a. a.com

On the solution:

  1. b. b.com

Stuck on a tricky one?

If you have a problem in BigCommerce orders, payments, inventory, webhooks, or fulfillment that you would rather hand off, this is the kind of work I do. Message me and we can work through it together.

Contact me on LinkedIn

Did this catch some negative stock?

If this stopped a SKU from overselling further, you can buy me a coffee. It is the best way to keep these field notes free and growing.

Buy me a coffee on Ko-fi

Back to all BigCommerce field notes