> For the complete documentation index, see [llms.txt](https://developer.esw.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.esw.com/customs-catalog-api/resources/postman-collection/invalid-product-remediation.md).

# Invalid Product Remediation

### <img src="/files/3Hzf3CWYWyHDFskuSRKJ" alt="" data-size="line"> What This Collection Does

This Postman collection covers the end-to-end workflow for finding and fixing **invalid products** in the Customs Catalog.

{% file src="/files/i0iB9SM7bDusFBRzAuUa" %}

{% hint style="info" %}
You need [**Postman**](https://postman.com/) to use this collection
{% endhint %}

An invalid product is one that has failed classification — something about its data is missing or wrong, which means it can't be assigned an HS code and may be held at customs. Common causes are:

* Missing HS code
* Missing country of origin
* Missing or invalid material description
* Product category not recognized by ESW

The collection walks through five phases in sequence:

```ruby
Assess → Investigate → Fix → Delete (if needed) → Verify
```

***

### Import the Collection

{% stepper %}
{% step %}

#### Open Postman&#x20;

{% endstep %}

{% step %}

#### Click the three dots <i class="fa-ellipsis">:ellipsis:</i>

Click **Import** from the three dots in the top left corner
{% endstep %}

{% step %}

#### Upload file <i class="fa-file-code">:file-code:</i>

Drag and drop `ESW_Invalid_Product_Remediation.postman_collection.json` into the import window, or click **Upload Files** and select it
{% endstep %}

{% step %}

#### Confirm

Click **Import** to confirm

{% hint style="info" %}
The collection will appear in your left sidebar under **Collections** as:

{% code expandable="true" %}

```ruby
ESW Customs Catalog — Invalid Product Remediation
```

{% endcode %}

Expand it.
{% endhint %}
{% endstep %}
{% endstepper %}

***

### Set Up Variables

The collection uses six variables so you only have to enter your credentials and codes once. Every request picks them up automatically.

1. Click the collection name in the sidebar
2. Click the **Variables** tab at the top of the right panel

<figure><img src="/files/BxAYyK1cWJMHZSnQlmD4" alt=""><figcaption></figcaption></figure>

3. Fill in the column for each variable

| Variable              | What to enter                        | Example                                                       |
| --------------------- | ------------------------------------ | ------------------------------------------------------------- |
| `baseUrl`             | The API base URL                     | `https://logistics-customscatalog-api.sandbox.eshopworld.com` |
| `token`               | Your Bearer JWT token                | `eyJhbGci...`                                                 |
| `tenantCode`          | Your retailer's tenant code          | `ACME`                                                        |
| `brandCode`           | Your 3-letter brand code             | `ACM`                                                         |
| `searchType`          | Leave as `Invalid`                   | `Invalid`                                                     |
| `invalidProductCount` | Leave blank — auto-filled by Phase 1 | *(leave empty)*                                               |

4. Click  `Save`

{% @code-walkthrough/kbd-shortcut keys="Cmd/Ctrl+S" label="" position="after" %}

***

### Authentication

The collection is configured with **Bearer Token** authentication at the collection level. This means every request in the collection automatically sends your token in the `Authorization` header — you don't need to set it per-request.

**To confirm it's set up:**

1. Click the collection name
2. Click the **Authorization** tab
3. You should see **Type: Bearer Token** and **Token: {{token}}**

If you need to use a different token for a specific request, open that request, go to its **Authorization** tab, and change **Inherit auth from parent** to **Bearer Token**, then enter the token directly.

**Token expiry:** JWT tokens are short-lived. If requests start returning `401 Unauthorized`, your token has expired. Get a new one from the ESW identity service and update the `token` variable.

***

### Remediation Workflow

The five phases are designed to be run in order. Here's the full picture before drilling into each one:

{% code expandable="true" %}

```ruby
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  Phase 1 — Assess                                           │
│  How many invalid products exist? Which brands?             │
│                          │                                  │
│                          ▼                                  │
│  Phase 2 — Investigate                                      │
│  What are the specific products? What's wrong with them?    │
│                          │                                  │
│                          ▼                                  │
│       ┌──────────────────┴──────────────────┐               │
│       │                                     │               │
│       ▼                                     ▼               │
│  Phase 3 — Fix                     Phase 4 — Delete         │
│  Data corrected upstream?          Products discontinued?   │
│  Reprocess to reclassify.          Remove them entirely.    │
│       │                                     │               │
│       └──────────────────┬──────────────────┘               │
│                          │                                  │
│                          ▼                                  │
│  Phase 5 — Verify                                           │
│  Did the count drop? How many remain?                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

{% endcode %}

***

### Phase 1 — Assess

**Goal:** Understand the scale of the problem before doing anything.

#### 1a. Health Check — Total Invalid Products

**What it does:** Calls `/api/v2/Home/TotalInvalidProducts` and returns the total invalid count across all brands, with a per-brand breakdown.

**Run this first, every time.** The test script automatically saves the count to the `{{invalidProductCount}}` variable so Phase 5 can calculate the before/after delta.

**What to look for in the response:**

```json
{
  "totalInvalidProductCount": 47,
  "invalidProductCountByBrand": [
    { "key": "ACM", "value": 32 },
    { "key": "ZEN", "value": 15 }
  ]
}
```

* If `totalInvalidProductCount` is `0` — you're done, the platform is clean
* If it's greater than zero — note which brands are affected and move to Phase 2

**Console output after running:**

```
⚠ 47 invalid products found.
  Brand ACM: 32 invalid
  Brand ZEN: 15 invalid
```

***

#### 1b. Health Check — Total Default Classified Products

**What it does:** Calls `/api/v2/Home/TotalDefaultProducts` and returns products that fell back to a generic HS code because they couldn't be auto-classified.

**Why it matters:** Default-classified products are not technically invalid — they will ship. But they may attract the wrong duty rate. Running this alongside 1a gives you the full picture.

**What to look for:**

```json
{
  "totalDefaultClassifiedProductCount": 12,
  "defaultClassifiedProductCountByBrand": [
    { "key": "ACM", "value": 12 }
  ]
}
```

A high default count usually means products were uploaded without HS codes. It's worth fixing, but it's lower urgency than invalid products.

***

### Phase 2 — Investigate

**Goal:** Find out exactly which products are invalid and what's wrong with them.

#### 2a. List Invalid Products (POST — paginated)

**What it does:** Calls `POST /api/v2/InvalidProducts/Invalid` with a request body and returns a paginated list of invalid products.

**This is your main investigation tool.** Use it to page through all invalid products for a brand before deciding what to fix.

**The request body:**

```json
{
  "pageNumber": 1,
  "pageSize": 20,
  "brandCode": "{{brandCode}}",
  "orderByField": "productCode",
  "isOrderByAsc": true
}
```

* Change `pageSize` to `50` or `100` if you want more results per page
* Remove `brandCode` entirely to see all brands at once
* Change `orderByField` to `name` or `lastModifiedDateTime` to sort differently

**What to look for in the response:**

```json
{
  "items": [
    {
      "productCode": "WMN-DRESS-001",
      "name": "Floral Wrap Dress",
      "hsCode": null,
      "countryOfOrigin": "IE"
    }
  ],
  "hasMoreResults": true,
  "continuationToken": "eyJwYWdlIjoy..."
}
```

* `hsCode: null` — missing HS code, most common cause of invalidity
* `countryOfOrigin: null` — missing country of origin
* `hasMoreResults: true` — there are more pages; run request 2b to fetch the next one

**Console output after running:**

```
Invalid products on this page: 20
  Missing HS code:           18 products
  Missing country of origin:  2 products
More results available — continuationToken saved.
```

***

#### 2b. List Invalid Products — Next Page

**What it does:** The same request as 2a but with `pageNumber: 2`. Keep incrementing the page number and re-running until `hasMoreResults` is `false`.

**Tip:** Rather than running this many times, increase `pageSize` in request 2a to a large number (e.g. `500`) to get all results at once.

***

#### 2c. List Invalid Products (GET — with filters)

**What it does:** Calls `GET /api/v2/Products/Invalid` with query parameters instead of a request body. Useful for quick one-off lookups directly from the URL bar.

**When to use this over 2a:** When you want to quickly check a specific brand without editing a request body, or when sharing a URL with a colleague. The response format is slightly different — it uses a standard `pagination` object rather than continuation tokens.

**Active query parameters:**

| Parameter      | Value           | Purpose             |
| -------------- | --------------- | ------------------- |
| `BrandCode`    | `{{brandCode}}` | Filter to one brand |
| `PageNumber`   | `1`             | First page          |
| `PageSize`     | `20`            | Results per page    |
| `IsOrderByAsc` | `true`          | Sort ascending      |

The greyed-out parameters (`TenantCode`, `SearchType`, `ContinuationToken`, `OrderByField`) are disabled by default. Click the checkbox next to any of them to enable and fill in a value.

***

#### 2d. Generate Error File (trigger async export)

**What it does:** Calls `POST /api/v2/InvalidProducts/Invalid/File/{tenantCode}/{searchType}` to trigger generation of a downloadable CSV error file.

**Important:** This only *triggers* the generation — it doesn't return the file. File generation is asynchronous. After getting a `200 OK` response, wait a few seconds, then run request 2e to download the file.

**Responses to expect:**

| Response         | Meaning                                                         |
| ---------------- | --------------------------------------------------------------- |
| `200 OK`         | File generation started — proceed to 2e                         |
| `204 No Content` | No invalid products exist for this tenant — no file to generate |

#### 2e. Download Error File

**What it does:** Calls `GET /api/v2/InvalidProducts/Invalid/File/{tenantCode}/{searchType}` to download the CSV file generated by 2d.

**How to save the file in Postman:**

1. Run the request
2. In the response panel, click **Save Response**
3. Click **Save to a file**
4. Choose a location on your computer

The CSV will contain one row per invalid product with columns for the product code, name, and the specific error reason — useful for handing off to the data team who needs to fix the source records.

**If you get a `400 Bad Request`:** The file isn't ready yet. Wait 10–15 seconds and try again.

***

#### 2f. Download Error File (Legacy — GET with query params)

**What it does:** An alternative to the 2d + 2e two-step process. Calls `GET /api/v2/Products/Invalid/File` directly with query parameters, returning a file without needing to trigger generation first.

**When to use this over 2d/2e:** When you want a quick download scoped to a brand rather than a tenant, or when you don't want to wait for async generation.

`PageSize` is set to `1000` by default to capture all invalid products in a single download. Adjust if needed.

***

### Phase 3 — Fix (Reprocess)

**Goal:** After the source data has been corrected and re-uploaded, trigger reprocessing so ESW attempts to classify the products again.

> **Important:** Reprocessing does not fix data problems — it retries classification using the data that's already in the system. You must correct and re-upload the product data first, then reprocess. If you reprocess without fixing the data, the same products will be invalid again.

All three reprocess requests return `202 Accepted` — this means the job has been **queued**, not completed. Processing is asynchronous. Wait 2–10 minutes (depending on catalog size) before running Phase 5 to verify.

***

#### 3a. Reprocess Invalid Products — by Brand

**What it does:** Calls `POST /api/v2/InvalidProducts/Reprocess?brandCode={{brandCode}}` to reprocess one brand's invalid products.

**Use this when:** A single brand's data team has corrected and re-uploaded their products.

The `brandCode` query parameter is pre-filled from your collection variables. Change it in the **Params** tab if you need to reprocess a different brand without updating the variable.

***

#### 3b. Reprocess All Brands (no filter)

**What it does:** Calls `POST /api/v2/InvalidProducts/Reprocess` with no `brandCode` parameter — reprocesses every brand on the platform at once.

**Use this when:** A platform-wide fix has been applied — for example, a new classification rule has been added that will now correctly classify products that were previously failing.

⚠️ This triggers reprocessing for every brand. On large catalogs it may cause significant processing load. Check with your ESW account team before running this in production during peak hours.

***

#### 3c. Reprocess Invalid Products — by Tenant

**What it does:** Calls `POST /api/v2/InvalidProducts/Reprocess/TenantCode?tenantCode={{tenantCode}}` to reprocess all invalid products for a specific tenant.

**Use this when:** You're operating in a multi-tenant setup and an entire tenant's data has been corrected, rather than just one brand within it.

***

### Phase 4 — Delete

**Goal:** Permanently remove invalid product records that should not be in the system at all.

> ⚠️ **This is irreversible.** Deleted records cannot be recovered. Only use this phase when the products genuinely need to be removed — for example, discontinued lines or test data that was accidentally uploaded to production.
>
> If your goal is to fix and re-classify the products, use **Phase 3 (Reprocess)** instead.

***

#### 4a. Delete Invalid Products — by Brand

**What it does:** Calls `POST /api/v2/InvalidProducts/Delete` with a request body specifying the brand and page size.

The pre-request script logs a warning to the Postman Console before executing:

```
⚠ About to delete ALL invalid products for brand: ACM
This action is IRREVERSIBLE. Ensure this is intentional.
```

**The request body:**

```json
{
  "pageNumber": 1,
  "pageSize": 1000,
  "brandCode": "{{brandCode}}"
}
```

`pageSize: 1000` is set deliberately high to delete all invalid records in a single call. Reduce it if you only want to delete a subset.

**Responses to expect:**

| Response        | Meaning                                  |
| --------------- | ---------------------------------------- |
| `200 OK`        | Records deleted successfully             |
| `404 Not Found` | No invalid products found for this brand |

### Phase 5 — Verify

**Goal:** Confirm that reprocessing or deletion has reduced the invalid product count.

#### 5a. Verify — Health Check After Fix

**What it does:** Re-runs the same health check as Phase 1. The test script automatically compares the new count against the value stored in `{{invalidProductCount}}` from Phase 1 and logs a summary.

**Console output examples:**

*All resolved:*

```
=== Remediation Summary ===
Before: 47 invalid products
After:  0 invalid products
✓ All invalid products resolved. Platform is clean.
```

*Partially resolved:*

```
=== Remediation Summary ===
Before: 47 invalid products
After:  15 invalid products
✓ Reduced by 32. 15 still need attention.
```

*No change:*

```
=== Remediation Summary ===
Before: 47 invalid products
After:  47 invalid products
⚠ Count unchanged. Check if reprocessing completed, or if product data corrections have been uploaded.
```

*Count went up:*

```
=== Remediation Summary ===
Before: 47 invalid products
After:  53 invalid products
⚠ Count increased by 6. New invalid products may have been uploaded.
```

**If the count hasn't dropped after reprocessing:** The source data has not been corrected. Go back to Phase 2, re-examine the error file, and confirm with the data team that the specific fields flagged (HS code, country of origin, material) have been fixed and the corrected catalog re-uploaded before reprocessing again.

***

### Run the Full Workflow with Collection Runner

The Collection Runner lets you run all requests in sequence automatically — useful for a morning check or a scheduled remediation run.

1. Hover over the collection name in the sidebar
2. Click the three dots
3. **Run** button appears

**Recommended settings:**

| Setting               | Value    | Reason                                                      |
| --------------------- | -------- | ----------------------------------------------------------- |
| **Iterations**        | `1`      | One pass through is enough for a remediation run            |
| **Delay**             | `500` ms | Gives the API a moment between calls                        |
| **Save responses**    | ✓ On     | Lets you review each response after the run                 |
| **Stop run on error** | ✓ On     | Stops if a request returns a non-2xx so you can investigate |

**Which folders to include:**

For a standard remediation run, include Phase 1, 2, and 5. Add Phase 3 or 4 based on what action you need to take. To exclude a folder, click the toggle next to it in the runner.

> **Do not include Phase 4 in automated runs.** The delete operation is destructive and should always require deliberate, manual execution.

**After the run completes:**

Click any request in the results to see its response, test results, and console output. Failed tests are highlighted in red.

***

### Common Scenarios

#### "I need to fix one brand's products"

1. Set `brandCode` variable&#x20;
2. Run **1a** to confirm the count and note it
3. Run **2a** to list the specific products and identify what's wrong
4. Run **2e** to download the error CSV and send to the data team
5. Once they've corrected and re-uploaded the data, run **3a** to reprocess
6. Wait 5 minutes, then run **5a** to verify the count dropped

***

#### "I need to clean up after a bad bulk upload"

1. Run **1a** to see which brands were affected
2. Run **2a** with no `brandCode` to see all invalid products across brands
3. Run **2f** to download a full error file scoped to the affected brands
4. Decide: are these recoverable (fix and reprocess) or junk (delete)?
5. If recoverable: fix the source data, re-upload, then run **3b** (all brands)
6. If junk: run **4a** for each affected brand, then **5a**

***

#### "Reprocessing isn't reducing the count"

This means the source data still has errors. Work through this checklist:

1. **Wait longer** — reprocessing on large catalogs can take 10–15 minutes
2. **Re-run 2a** — check if the same products are still listed; if so, the data hasn't been corrected
3. **Check the error file (2e)** — look at the specific error reasons for the products still showing as invalid
4. **Confirm with the data team** — verify they corrected *all* the flagged fields, not just some
5. **Confirm the re-upload happened** — a corrected file that was never uploaded won't help
6. Once confirmed, run **3a** again

***

#### "The invalid count went up after reprocessing"

New invalid products have been uploaded since you started the remediation run. This is common in active catalogs.

1. Run **2a** again to see the new invalid products
2. Check `lastModifiedDateTime` — any products modified *after* your Phase 1 run are newly introduced
3. Investigate the new batch separately and trace it back to the most recent upload

***

#### "I want to run this every morning as a health check"

Set up a Postman Monitor:

1. Click the **...** menu next to the collection name
2. Select **Monitor Collection**
3. Set the schedule (e.g. daily at 8am)
4. Select **Phase 1 only** in the folder selection
5. Add your email to receive a notification when tests fail

This gives you a daily alert if invalid products appear without needing to open Postman manually.

***

### Troubleshooting

**`401 Unauthorized` on every request**\
Your token has expired. Get a new JWT from the ESW identity service and update the `token` variable in the collection.

***

**`400 Bad Request` on the list requests**\
Check that `pageNumber` and `pageSize` are both present in the request body (2a/2b) or as query parameters (2c). These are required fields. Also verify `brandCode` is a valid 3-letter code — full brand names are not accepted.

***

**`400 Bad Request` on the file download (2e)**\
The error file from 2d isn't ready yet. Wait 10–15 seconds and retry. If it continues to fail after a minute, re-run 2d to trigger generation again.

***

**`404 Not Found` on delete (4a)**\
No invalid products were found for this brand — they may have already been deleted or successfully reprocessed. Run 1a to confirm the current count.

***

**`202 Accepted` on reprocess but count doesn't drop**\
Reprocessing is async — wait 5–10 minutes before running Phase 5. If the count still hasn't dropped after waiting, the source product data has not been corrected. See the "Reprocessing isn't reducing the count" scenario in Section 14.

***

**Variables showing `{{variableName}}` literally in requests**\
The variable hasn't been set. Click the collection name → **Variables** tab and make sure all **Current Value** fields are filled in, then click **Save**.

***

**Can't find the Postman Console**\
Go to **View → Show Postman Console** in the menu bar, or press `Alt + Ctrl + C` on Windows/Linux or `Cmd + Option + C` on Mac. The console must be open before you run a request to capture its output — output from previous runs is not retained.

<br>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developer.esw.com/customs-catalog-api/resources/postman-collection/invalid-product-remediation.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
