> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kibocommerce.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bulk Inventory Deletion

> How to safely delete inventory records in bulk using the Kibo Inventory APIs, including dry runs, async job monitoring, batch sizing, and self-throttling.

This guide walks through how to safely delete inventory records in bulk using Kibo's Inventory APIs — including how to structure your requests, validate before executing, monitor deletion jobs, and self-throttle your requests to avoid HTTP `429 Too Many Requests` errors.

<CardGroup cols={2}>
  <Card title="Inventory API Overview" icon="book-open" href="/pages/inventory-api-overview" horizontal>
    Best practices, job management, and segmentation via the Inventory API
  </Card>

  <Card title="Inventory Export File" icon="file-export" href="/pages/inventory-export-file" horizontal>
    Use Location Export files to build a complete inventory deletion list
  </Card>

  <Card title="API Best Practices" icon="gauge" href="/pages/api-best-practices" horizontal>
    Rate limiting, retry logic, and request patterns for Kibo APIs
  </Card>

  <Card title="Get Inventory (POST)" icon="code" href="/api-reference/inventory/get-inventory-post" horizontal>
    Look up current inventory by product identifier before deleting
  </Card>
</CardGroup>

***

## Overview

Bulk inventory deletion in Kibo is handled through two API endpoints that share the same asynchronous job-based execution model. Both endpoints return immediately — the actual deletion work happens in the background via Kibo's job queue. This means there is no risk of HTTP timeouts for large deletions, and you can track progress through the job IDs returned in the response.

**When you should use these APIs:**

* Removing discontinued product lines across all or selected locations
* Clearing out inventory for a tenant migration or re-platforming effort
* Deleting a large set of SKUs identified by a known naming convention

<Warning>
  Deletion is permanent. Always use `dryRun: true` first to verify scope before executing a live deletion.
</Warning>

## The Two Deletion Endpoints

### Option 1: Delete — Single-Product Pattern

**[`POST /api/commerce/inventory/v5/inventory/delete`](/api-reference/modifyinventory/delete-inventory)**

Use this endpoint when targeting **one product identifier** — a single `partNumber`, `upc`, or `sku` — across one or all locations. It supports basic regex operators in the identifier field (`.*+?^$[]`), making it well-suited for pattern-based deletion (e.g., all products whose part number starts with a given prefix).

#### Request Body

```json theme={null}
{
  "dryRun": true,
  "allLocations": true,
  "partNumber": "DISC-2023-.*",
  "locationCodes": [],
  "explicit": false
}
```

#### Field Reference

| Field           | Type             | Description                                                                                                            |
| --------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `dryRun`        | boolean          | When `true`, reports what would be deleted without making any changes. **Always start here.**                          |
| `allLocations`  | boolean          | When `true`, targets all locations for the tenant. Overrides `locationCodes`.                                          |
| `locationCodes` | array of strings | Scope the deletion to a specific set of location codes. Ignored when `allLocations` is `true`.                         |
| `partNumber`    | string           | The product part number. Supports basic regex operators: `.*+?^$[]`                                                    |
| `upc`           | string           | Alternative identifier. Supports the same regex operators.                                                             |
| `sku`           | string           | Alternative identifier. Supports the same regex operators.                                                             |
| `explicit`      | boolean          | When `true`, the response includes full per-item detail (inventory IDs, location IDs, audit IDs). Useful for auditing. |

<Note>
  Provide only one of `partNumber`, `upc`, or `sku` per request. This endpoint is designed for single-product targeting.
</Note>

***

### Option 2: Delete Items — Multi-Product Bulk Pattern

**[`POST /api/commerce/inventory/v5/inventory/deleteItems`](/api-reference/modifyinventory/delete-items)**

Use this endpoint when you have a **list of multiple products** to delete in one operation. The `items` array accepts individual product identifiers — each specified by `partNumber`, `upc`, or `sku`. Regex operators are supported per item.

#### Request Body

```json theme={null}
{
  "dryRun": false,
  "allLocations": true,
  "items": [
    { "upc": "UPC-001" },
    { "upc": "UPC-002" },
    { "partNumber": "PART-003" },
    { "sku": "SKU-004" }
  ]
}
```

#### Field Reference

| Field           | Type             | Description                                                                                                     |
| --------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `dryRun`        | boolean          | When `true`, reports what would be deleted without making changes.                                              |
| `allLocations`  | boolean          | When `true`, targets all locations. Overrides `locationCodes`.                                                  |
| `locationCodes` | array of strings | Scope to specific locations when not using `allLocations`.                                                      |
| `items`         | array            | List of products to delete. Each item may specify `partNumber`, `upc`, or `sku`. Regex operators are supported. |
| `explicit`      | boolean          | When `true`, returns full per-item deletion detail.                                                             |

***

## How Deletion Actually Works (Async Job Model)

Both endpoints operate **asynchronously**. When you submit a deletion request, Kibo:

1. Validates the request.
2. Enqueues **one background job per location** affected by the deletion.
3. Returns immediately with a response — no waiting for the deletion to complete.

This architecture is why these endpoints are safe to use for large-scale operations involving millions of records or hundreds of locations. There is no HTTP timeout risk on the deletion request itself.

#### Example Response

```json theme={null}
{
  "dryRun": false,
  "totalProductsDeleted": 312,
  "totalInventoryEntriesDeleted": 4245251,
  "totalLocationsAffected": 312,
  "totalAuditsDeleted": 0,
  "totalPickWavesDeleted": 0,
  "itemsDeleted": [...],
  "jobIDs": [12345, 12346, 12347]
}
```

<Note>
  The `jobIDs` array contains one job ID per affected location. For large tenants with many locations, this list can be long. Store all job IDs returned — you will use them to monitor progress.
</Note>

***

## Monitoring Deletion Progress

Since deletion runs in the background, use the **[Get Job API](/api-reference/inventoryjob/get-job)** to poll each job until it reaches a terminal status.

```
GET /api/commerce/inventory/v1/queue/{jobID}
```

To retrieve all jobs at once (useful when you have many job IDs to review), use the **[Get Jobs API](/api-reference/inventoryjob/get-jobs)**:

```
GET /api/commerce/inventory/v1/queue
```

#### Job Status Values

| Status    | Meaning                                                           |
| --------- | ----------------------------------------------------------------- |
| `PENDING` | Job is queued and has not started yet.                            |
| `WORKING` | Job is actively being processed.                                  |
| `SUCCESS` | Job completed successfully.                                       |
| `FAILED`  | Job encountered an error. Check the `messages` field for details. |

#### Example Polling Approach (Pseudocode)

```
for each jobID in jobIDs:
    repeat:
        response = GET /api/commerce/inventory/v1/queue/{jobID}
        if response.status == "SUCCESS" or response.status == "FAILED":
            log result and stop polling this job
        else:
            wait 10–30 seconds before polling again
```

<Tip>
  Do not poll at a rapid fixed interval. Poll every 10–30 seconds per job using exponential backoff, particularly if you have many jobs to track simultaneously. Excessive polling of the job endpoint itself consumes your API rate limit quota.
</Tip>

***

## Step-by-Step: Recommended Execution Workflow

### Step 1 — Identify Your Scope

Decide which products need to be deleted and how to identify them:

* Do they share a naming pattern (prefix, suffix, regex match)? → Use the **Delete** endpoint with a `partNumber`/`upc`/`sku` regex.
* Do you have a discrete list of product identifiers? → Use the **Delete Items** endpoint with an `items` array.
* Are you deleting across all locations, or specific ones? → Set `allLocations: true` or populate `locationCodes`.

#### How to Get a Reliable Full Inventory List Before a Bulk Deletion

There are several paths available, depending on your current tenant configuration and baseline data set:

| Situation                                                          | Recommended approach                                                                                                                                                      |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The full list of the tenant's product IDs is known (API-only path) | Use [Get Inventory (POST)](/api-reference/inventory/get-inventory-post) per location with [pagination](/pages/api-best-practices), scoped to known identifiers            |
| The tenant already receives daily inventory export files           | Use the most recent **Location Export** file — it is the authoritative full-tenant inventory list                                                                         |
| The tenant does not have exports set up yet                        | Contact [Kibo Support](https://help.kibocommerce.com/) to enable exports, or use your own catalog/ERP as the source of product identifiers to query the Get Inventory API |

The cleanest path for a bulk deletion project is: get the Location Export file → parse all `UPC`/`PartNumber`/`SKU` values from it → use that list as the input to the bulk delete operation. This gives you a provable, timestamped snapshot of exactly what was in inventory at the time of deletion, and a clean audit record.

##### Option A: The Inventory Export File

**[Inventory Export File documentation](/pages/inventory-export-file)**

The **Location Export** is the most reliable way to get a complete, authoritative list of every item at every location across your entire tenant. It includes key fields such as `PartNumber`, `UPC`, `SKU`, quantity on hand, quantity available, safety stock, floor quantity, and `locationCode` for every record.

**The tradeoff:** The export is not on-demand via API. It is a **scheduled daily file** delivered by Kibo to your configured drop point (such as an SFTP site or cloud storage destination). You cannot trigger it yourself via an API call.

* If your tenant already has inventory exports enabled and is receiving daily files, the most recent Location Export file is the best starting point — it gives you a clean, parseable list of all product identifiers per location.
* If the tenant does not have exports enabled, you need to contact [Kibo Support](https://help.kibocommerce.com/) to enable the feature and set up a delivery destination.

**Two export types:**

| **Export Type**      | **What it contains**                                         | **Best for**                          |
| -------------------- | ------------------------------------------------------------ | ------------------------------------- |
| **Location Export**  | Every item at every location, with location codes            | Building a per-location deletion list |
| **Aggregate Export** | Total quantities across all locations, no location breakdown | Understanding overall scope           |

For building a deletion list you will use against the bulk delete endpoint, the **Location Export** is what you want — it gives you the product identifiers and the [location codes](/developer-guides/location-admin) together.

##### Option B: The Get Inventory (POST) API

**[Get Inventory (POST) API](/api-reference/inventory/get-inventory-post)**

The Get Inventory API supports [pagination (`pageSize`)](/pages/api-best-practices) and location filtering. However, there is a critical constraint: **the `items` array in the request body is required**, and it specifies which products you want to look up. This is an inquiry/lookup API — not a "dump everything" API. You cannot submit an empty request and get all inventory back.

* If you already know the product identifiers (e.g., from your ERP or catalog system), you can paginate through the Get Inventory API per location to confirm what exists in Kibo before building your deletion list.
* If you do not have a known product list, the API alone cannot enumerate all inventory records without already knowing what to query for.

### Step 2 — Run a Dry Run First

Always submit your request with `dryRun: true` before any live execution.

```json theme={null}
{
  "dryRun": true,
  "allLocations": true,
  "partNumber": "DISC-2023-.*"
}
```

Review the response carefully:

* `totalProductsDeleted` — confirms how many products match.
* `totalInventoryEntriesDeleted` — shows the volume of records that will be removed.
* `totalLocationsAffected` — tells you how many location-level jobs will be created.
* `itemsDeleted` — if `explicit: true` was set, inspect individual matches before proceeding.

Do not proceed to Step 3 until the dry run results match your expectations.

### Step 3 — Batch Your Live Requests (If Using Delete Items)

If you are using the **Delete Items** endpoint `/api/commerce/inventory/v5/inventory/deleteItems` with a large list of products, do not send all items in a single request. Break your list into batches.

<Warning>
  **Recommended batch size: 500 items per request.**

  The request returns immediately once jobs are enqueued, but the enqueuing itself is synchronous and bounded by database and process timeout limits. Staying at or below 500 items per batch keeps your requests well within the timeout ceiling, even on tenants with a large number of locations.
</Warning>

<Warning>
  **Hard maximum: 1,000 items per request.** The Delete Items endpoint will reject requests that exceed this ceiling under any circumstances.
</Warning>

**Between batches, add a deliberate delay of at least 5–10 seconds.** This serves two purposes: it gives the job queue time to begin processing the previous batch, and it keeps your sustained request rate below the per-minute API rate limit.

**Recommended approach:**

* Split your full product list into batches of **500 items per request**.
* Do not exceed **1,000 items per batch under any circumstances**.
* Submit one batch at a time. Collect all `jobIDs` returned in the response before proceeding.
* **Wait at least 5–10 seconds between batches.**
* Do not use batch completion (all jobs at `SUCCESS`) as a gate before submitting the next batch — the jobs run asynchronously and may take time to work through the queue. Pace by time delay, not by job status.

### Step 4 — Submit the Live Deletion with `dryRun: false`

Once the dry run is validated and your batch strategy is in place, submit with `dryRun: false`. Collect and store all `jobIDs` returned.

```json theme={null}
{
  "dryRun": false,
  "allLocations": true,
  "partNumber": "DISC-2023-.*"
}
```

### Step 5 — Monitor All Jobs to Completion

Poll each job ID using the [Get Job API](/api-reference/inventoryjob/get-job). Log all `FAILED` jobs and their `messages` for investigation. A failed job does not automatically retry — you will need to re-submit a targeted deletion request for any failed scope.

***

## Self-Throttling to Avoid HTTP 429 Errors

Kibo enforces **rate limiting** per API route to protect platform stability for all tenants. If you exceed the allowed request rate, subsequent requests will be rejected with an `HTTP 429 Too Many Requests` response until the restriction window expires.

### Understanding the 429 Response

```
HTTP/1.1 429 Too Many Requests
Retry-After: 60
```

The `Retry-After` response header tells you exactly how many seconds to wait before making another request. Possible values are **60, 900, 1800, 2700, or 3600 seconds** (1 minute up to 60 minutes). Do not continue making requests during this window — they will also be rejected, and sustained violations can extend the restriction period.

### How Rate Limits Work

Kibo applies limits at both the **per-minute (RPM)** and **per-hour (RPH)** level for each API route. Both limits count against the same requests:

* **Minute limits** reset at the start of each new minute.
* **Hourly limits** operate on a rolling 60-minute window divided into 15-minute buckets.

**Burst behavior:** You can send requests up to the maximum RPM rate, but only for as long as your hourly budget allows. For example, if the inventory route allows 50 RPM and 200 RPH, you can sustain 50 RPM for a maximum of 4 minutes before exhausting your hourly budget. Plan accordingly — do not use your entire hourly quota in a short burst.

<Tip>
  To view the exact rate limits for your tenant per API route, go to **Dev Center** > **API** > **Limits**. If you are within limits, the status shows **OK** in green. If throttled, it shows **Throttled** in red.
</Tip>

### Practical Self-Throttling Rules for Bulk Deletion

| **Rule**                                                                                                            | **Why It Matters**                                                                                                                                                                                                                                                            |
| ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Send one batch at a time.** Never fire multiple deletion requests simultaneously.                                 | Each request to `delete` or `deleteItems` generates jobs and counts against your per-minute and hourly API rate limits.                                                                                                                                                       |
| **Add deliberate delays between batches.** Wait at least a few seconds between requests, longer for large lists.    | This keeps your sustained request rate below the per-minute limit without exhausting your hourly budget too quickly.                                                                                                                                                          |
| **Honor the `Retry-After` header immediately.** If you receive a 429, stop all requests and wait the full duration. | Continuing to send requests during a restriction window will not succeed and may extend the throttle period.                                                                                                                                                                  |
| **Poll job status at low frequency.** Poll each job every 10–30 seconds, not continuously.                          | Job status poll requests count against the same `/api/commerce/inventory/*` rate limit pool as your deletion requests.                                                                                                                                                        |
| **Schedule large deletions during non-peak hours.**                                                                 | For US tenants, non-peak hours are **05:00–11:00 UTC**. For EU tenants, **22:00–04:00 UTC**. Running bulk operations during these windows reduces contention. Non-peak hours are calculated in UTC — schedule jobs in UTC to avoid Daylight Saving Time shifting your window. |

### Handling a 429 — Retry Logic Pattern (Pseudocode)

```
function submitWithRetry(requestPayload):
    attempt = 0
    while attempt < MAX_RETRIES:
        response = POST /api/commerce/inventory/v5/inventory/deleteItems (requestPayload)

        if response.status == 200:
            return response

        if response.status == 429:
            retryAfterSeconds = response.headers["Retry-After"]
            log("Rate limited. Waiting " + retryAfterSeconds + " seconds before retry.")
            wait(retryAfterSeconds)     // Respect the exact header value
            attempt += 1

        if response.status == 500 or other error:
            wait(exponentialBackoff(attempt))   // Add delay before retrying non-429 errors
            attempt += 1

    raise Error("Max retries exceeded")
```

Per the [API Best Practices](/pages/api-best-practices), always add delay before retrying on non-200 responses. Use exponential backoff or a similar strategy rather than retrying immediately.

***

## Endpoint and Capability Summary

| **Feature**              | **Delete (Single Pattern)**                  | **Delete Items (Bulk Pattern)**        |
| ------------------------ | -------------------------------------------- | -------------------------------------- |
| **Endpoint**             | `POST /v5/inventory/delete`                  | `POST /v5/inventory/deleteItems`       |
| **Products per request** | 1 (with regex support)                       | Many (array of identifiers)            |
| **Execution model**      | Async — one job per location                 | Async — one job per location           |
| **Timeout risk**         | None — returns immediately                   | None — returns immediately             |
| **Dry run support**      | Yes                                          | Yes                                    |
| **Regex support**        | Yes                                          | Yes (per item)                         |
| **Location scoping**     | `allLocations` or `locationCodes`            | `allLocations` or `locationCodes`      |
| **Progress tracking**    | Job IDs → Get Job API                        | Job IDs → Get Job API                  |
| **Best for**             | Pattern-based deletion of one product family | Deleting a known list of multiple SKUs |

## Quick Reference: API Links

| Resource                           | Link                                                             |
| ---------------------------------- | ---------------------------------------------------------------- |
| Delete Inventory (single-product)  | [API Reference](/api-reference/modifyinventory/delete-inventory) |
| Delete Items (bulk)                | [API Reference](/api-reference/modifyinventory/delete-items)     |
| Get Job (monitor a single job)     | [API Reference](/api-reference/inventoryjob/get-job)             |
| Get Jobs (list all jobs)           | [API Reference](/api-reference/inventoryjob/get-jobs)            |
| Get Inventory (POST)               | [API Reference](/api-reference/inventory/get-inventory-post)     |
| API Best Practices (rate limiting) | [API Best Practices](/pages/api-best-practices)                  |
| Inventory Export File              | [Guide](/pages/inventory-export-file)                            |

## Pre-Deletion Checklist

Before running a bulk inventory deletion, confirm the following:

* [ ] Dry run (`dryRun: true`) has been executed and the scope has been reviewed.
* [ ] The `totalProductsDeleted` and `totalInventoryEntriesDeleted` values in the dry run match expectations.
* [ ] If using Delete Items, the full product list has been split into batches of 500 or fewer.
* [ ] A delay between batch submissions is built into the process.
* [ ] A retry mechanism that respects the `Retry-After` header is implemented.
* [ ] Job IDs from the live request have been captured and are being monitored via the Get Job API.
* [ ] The deletion is scheduled during non-peak hours where possible (05:00–11:00 UTC for US tenants; 22:00–04:00 UTC for EU tenants).
* [ ] Any jobs returning `FAILED` status have been reviewed and re-submitted as needed.
