> 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/tutorials/classifying-products.md).

# Classifying Products

{% hint style="info" icon="rocket-launch" %}

### Prerequisites

* A valid Bearer JWT token
* Your `brandCode` or `tenantCode`
* Products already uploaded
  {% endhint %}

Classification is the process of mapping your products to HS (Harmonised System) codes — the international standard used by customs authorities to calculate duties and taxes. Every product that ships internationally must have a valid classification.

```
Chapter
  └── Section
        └── SubSection
              └── Classification (HS Code group)
```

***

{% stepper %}
{% step %}

### Understand the Classification Hierarchy

Before querying, it is useful to understand the four levels and the fields that identify each.

<table><thead><tr><th>Level</th><th>Object</th><th>Description</th><th data-hidden>Key fields</th></tr></thead><tbody><tr><td>Chapter</td><td><code>Chapter</code></td><td>Broad product category grouping, e.g. <code>"Textiles"</code></td><td><code>code</code>, <code>name</code></td></tr><tr><td>Section</td><td><code>Section</code></td><td>Sub-group within a chapter</td><td><code>code</code>, <code>name</code>, <code>chapterCode</code></td></tr><tr><td>SubSection</td><td><code>SubSection</code></td><td>Narrower product type within a section</td><td><code>code</code>, <code>name</code>, <code>chapterCode</code>, <code>sectionCode</code>, <code>parentCode</code></td></tr><tr><td>Classification</td><td><code>ClassificationDto</code></td><td>The actual HS code group, containing duty and tax data per region</td><td><code>classificationCode</code>, <code>classificationId</code>, <code>classificationDesc</code></td></tr></tbody></table>

A `classificationDesc` is typically a concatenation of the Chapter, Section, and SubSection names — useful for displaying the full path to a user without reconstructing it.

***

{% endstep %}

{% step %}

### Browse All Chapters

Start at the top of the hierarchy by fetching all available chapters. This gives you the full list of broad product categories to navigate into.

```http
GET /api/v2/Classifications/Chapters
Authorization: Bearer <token>
```

**Example response:**

```json
[
  {
    "id": "chap-001",
    "code": "APPAREL",
    "name": "Apparel & Accessories",
    "lastModifiedDateTime": "2024-01-15T09:00:00Z"
  },
  {
    "id": "chap-002",
    "code": "ELECTRONICS",
    "name": "Electronics & Technology",
    "lastModifiedDateTime": "2024-01-15T09:00:00Z"
  }
]
```

**Chapter fields:**

| Field                  | Description                                         |
| ---------------------- | --------------------------------------------------- |
| `id`                   | Internal Cosmos ID                                  |
| `code`                 | Chapter code — used in subsequent hierarchy queries |
| `name`                 | Human-readable chapter name                         |
| `lastModifiedDateTime` | When the chapter was last updated                   |

Note the `code` value for the chapter your product belongs to — you will need it for Step 3.

***

{% endstep %}

{% step %}

### Browse Sections Within a Chapter

Once you have a `chapterCode`, fetch the sections that sit beneath it.

```http
GET /api/v2/Classifications/Section?chapterCode={chapterCode}
Authorization: Bearer <token>
```

**Example:**

```http
GET /api/v2/Classifications/Section?chapterCode=APPAREL
Authorization: Bearer <token>
```

**Example response:**

{% code expandable="true" %}

```json
[
  {
    "id": "sec-101",
    "code": "LEATHER",
    "name": "Leather Goods",
    "chapterCode": "APPAREL",
    "lastModifiedDateTime": "2024-01-15T09:00:00Z"
  },
  {
    "id": "sec-102",
    "code": "FOOTWEAR",
    "name": "Footwear",
    "chapterCode": "APPAREL",
    "lastModifiedDateTime": "2024-01-15T09:00:00Z"
  }
]
```

{% endcode %}

**Section fields:**

| Field         | Description                               |
| ------------- | ----------------------------------------- |
| `code`        | Section code — used in subsequent queries |
| `name`        | Human-readable section name               |
| `chapterCode` | Parent chapter code                       |

If `chapterCode` is omitted from the query, all sections across all chapters are returned.

***

{% endstep %}

{% step %}

### Browse Sub-Sections

Narrow further by fetching the sub-sections within a chapter and section pairing.

```http
GET /api/v2/Classifications/SubSection?chapterCode={chapterCode}&sectionCode={sectionCode}
Authorization: Bearer <token>
```

**Example:**

```http
GET /api/v2/Classifications/SubSection?chapterCode=APPAREL&sectionCode=LEATHER
Authorization: Bearer <token>
```

**Example response:**

{% code expandable="true" %}

```json
[
  {
    "id": "sub-201",
    "code": "WALLETS",
    "name": "Wallets & Small Leather Goods",
    "chapterCode": "APPAREL",
    "sectionCode": "LEATHER",
    "parentCode": "APPARELLEATHER",
    "lastModifiedDateTime": "2024-01-15T09:00:00Z"
  },
  {
    "id": "sub-202",
    "code": "BAGS",
    "name": "Handbags & Purses",
    "chapterCode": "APPAREL",
    "sectionCode": "LEATHER",
    "parentCode": "APPARELLEATHER",
    "lastModifiedDateTime": "2024-01-15T09:00:00Z"
  }
]
```

{% endcode %}

**SubSection fields:**

| Field         | Description                                                      |
| ------------- | ---------------------------------------------------------------- |
| `code`        | Sub-section code — used when querying `ChapterClassifications`   |
| `name`        | Human-readable sub-section name                                  |
| `chapterCode` | Grandparent chapter code                                         |
| `sectionCode` | Parent section code                                              |
| `parentCode`  | Concatenation of `chapterCode` + `sectionCode` — used internally |

Either or both query parameters can be omitted to broaden the result set.

***

{% endstep %}

{% step %}

### Find the Right Classification Code

With a chapter, section, and sub-section identified, query `ChapterClassifications` to find the specific classification codes available at that position in the hierarchy.

<details>

<summary>Option A — Query Parameters (GET)</summary>

```http
GET /api/v2/Classifications/ChapterClassifications
  ?ChapterCode=APPAREL
  &SectionCode=LEATHER
  &SubSectionCode=WALLETS
  &RegionCode=EU
  &PageNumber=1
  &PageSize=50
Authorization: Bearer <token>
```

**All query parameters:**

| Parameter            | Type    | Required | Description                                                                 |
| -------------------- | ------- | -------- | --------------------------------------------------------------------------- |
| `PageNumber`         | integer | ✅        | Page number                                                                 |
| `PageSize`           | integer | ✅        | Results per page                                                            |
| `RegionCode`         | string  | ❌        | Filter classifications to those applicable in a region, e.g. `"EU"`, `"US"` |
| `ChapterCode`        | string  | ❌        | Filter by chapter                                                           |
| `SectionCode`        | string  | ❌        | Filter by section                                                           |
| `SubSectionCode`     | string  | ❌        | Filter by sub-section                                                       |
| `ClassificationCode` | string  | ❌        | Look up a specific classification code directly                             |
| `OrderByField`       | string  | ❌        | Sort field                                                                  |
| `IsOrderByAsc`       | boolean | ❌        | Sort direction                                                              |

</details>

<details>

<summary>Option B — Request Body (POST) with Continuation Token</summary>

For large classification trees or when you need to iterate results programmatically, use the POST variant which supports continuation tokens:

```http
POST /api/v2/Classifications/LoadChapterClassifications
Authorization: Bearer <token>
Content-Type: application/json

{
  "chapterCode": "APPAREL",
  "sectionCode": "LEATHER",
  "subSectionCode": "WALLETS",
  "regionCode": "EU",
  "takeCount": 50,
  "continuationToken": null
}
```

Pass the `continuationToken` from each response into the next request until all results are retrieved (same pattern as Tutorial 4 Step 4).

**Request body fields:**

| Field                | Type    | Description                                 |
| -------------------- | ------- | ------------------------------------------- |
| `regionCode`         | string  | Region to filter by                         |
| `chapterCode`        | string  | Chapter filter                              |
| `sectionCode`        | string  | Section filter                              |
| `subSectionCode`     | string  | Sub-section filter                          |
| `classificationCode` | string  | Exact classification code lookup            |
| `takeCount`          | integer | Number of results to return per call        |
| `continuationToken`  | string  | Token from previous response for pagination |

**Example response (both GET and POST):**

{% code expandable="true" %}

```json
{
  "currentPage": 1,
  "pageCount": 2,
  "pageSize": 50,
  "rowCount": 64,
  "results": [
    {
      "classificationId": "clf-4201",
      "classificationCode": "4202.32",
      "classificationDesc": "Apparel & Accessories > Leather Goods > Wallets & Small Leather Goods"
    },
    {
      "classificationId": "clf-4203",
      "classificationCode": "4202.33",
      "classificationDesc": "Apparel & Accessories > Leather Goods > Wallets & Small Leather Goods"
    }
  ]
}
```

{% endcode %}

**Classification fields:**

| Field                | Description                                                   |
| -------------------- | ------------------------------------------------------------- |
| `classificationId`   | Internal platform ID — used to look up regional duty/tax data |
| `classificationCode` | The HS code (or HS code group) for this classification        |
| `classificationDesc` | Full human-readable path through the hierarchy                |

</details>

***

{% endstep %}

{% step %}

### View Regional Duty and Tax Data for a Classification

Each classification carries duty and VAT data that varies by region. To see what rates apply in each region for a specific classification:

```http
GET /api/v2/Classifications/{id}/Regions
Authorization: Bearer <token>
```

Where `{id}` is the `classificationId` from Step 5.

**Example:**

```http
GET /api/v2/Classifications/clf-4201/Regions
Authorization: Bearer <token>
```

**Example response:**

{% code expandable="true" %}

```json
[
  {
    "id": "region-eu-clf4201",
    "classificationId": "clf-4201",
    "classificationCode": "4202.32",
    "regionCode": "EU",
    "mfnDuty": 3.7,
    "generalDuty": 3.7,
    "additionalDuty": 0.0,
    "vatBandId": 12,
    "vatPercentageRate": 20.0,
    "lastModifiedBy": "admin"
  },
  {
    "id": "region-us-clf4201",
    "classificationId": "clf-4201",
    "classificationCode": "4202.32",
    "regionCode": "US",
    "mfnDuty": 5.3,
    "generalDuty": 5.3,
    "additionalDuty": 7.5,
    "vatBandId": 9,
    "vatPercentageRate": 0.0,
    "lastModifiedBy": "admin"
  }
]
```

{% endcode %}

**ClassificationRegion fields:**

| Field                        | Description                                       |
| ---------------------------- | ------------------------------------------------- |
| `classificationId`           | The classification this region record belongs to  |
| `classificationCode`         | The HS code                                       |
| `regionCode`                 | The region these rates apply to                   |
| `mfnDuty`                    | Most Favoured Nation duty rate (%)                |
| `generalDuty`                | General duty rate (%)                             |
| `additionalDuty`             | Any additional duty applied on top (%)            |
| `vatBandId`                  | Reference to the VAT band configuration           |
| `vatPercentageRate`          | VAT rate for this region (%)                      |
| `previousClassificationCode` | The prior code if this classification was updated |
| `lastModifiedBy`             | Who last modified this record                     |

***

{% endstep %}

{% step %}

### Assign and Save Classification Region Data

Once you have selected the correct classification, you can save or update the regional duty/tax data associated with it. This is typically used to correct or override rates.

#### Save Regions for a Classification

{% code expandable="true" %}

```http
POST /api/v2/Classifications/regions/save
Authorization: Bearer <token>
Content-Type: application/json

[
  {
    "classificationId": "clf-4201",
    "classificationCode": "4202.32",
    "regionCode": "EU",
    "mfnDuty": 3.7,
    "generalDuty": 3.7,
    "additionalDuty": 0.0,
    "vatBandId": 12,
    "vatPercentageRate": 20.0
  },
  {
    "classificationId": "clf-4201",
    "classificationCode": "4202.32",
    "regionCode": "US",
    "mfnDuty": 5.3,
    "generalDuty": 5.3,
    "additionalDuty": 7.5,
    "vatBandId": 9,
    "vatPercentageRate": 0.0
  }
]
```

{% endcode %}

You can also submit region data directly against the `POST /api/v2/Classifications` endpoint, which queues the data for asynchronous processing and returns `202 Accepted`.

***

{% endstep %}

{% step %}

### Update Default Classification on Products

Products that the platform could not match to a specific classification are flagged with `defaultClassification: true`. Once you have identified the correct classification in Steps 2–5, you should clear this flag to indicate the product now has an accurate assignment.

#### View Products Currently Using Default Classifications

```http
GET /api/v2/Products/DefaultClassificationFile
  ?TenantCode=my-tenant-001
  &DefaultClassification=true
  &PageNumber=1
  &PageSize=100
Authorization: Bearer <token>
```

This downloads a file of all products currently using a default classification, which you can use to identify which products need attention.

#### Update Products to Clear the Default Classification Flag

Once you are satisfied with the classification data for a set of products, mark them as no longer using a default

```http
PUT /api/v2/Products/DefaultClassification
Authorization: Bearer <token>
Content-Type: application/json

{
  "brandCode": "MBR",
  "tenantCode": "my-tenant-001",
  "userName": "classifier@mybrand.com",
  "defaultClassification": false,
  "productCatalogIdList": [
    "SKU-00123",
    "SKU-00124",
    "SKU-00125"
  ]
}
```

**Request body fields:**

| Field                   | Type             | Description                                                |
| ----------------------- | ---------------- | ---------------------------------------------------------- |
| `brandCode`             | string           | Brand code                                                 |
| `tenantCode`            | string           | Tenant code                                                |
| `userName`              | string           | User performing the update — recorded in audit trail       |
| `defaultClassification` | boolean          | Set to `false` to clear the default flag, `true` to set it |
| `productCatalogIdList`  | array of strings | List of product SKUs to update                             |

#### Import a Default Classification File

For bulk updates, you can import a pre-prepared file instead of calling the PUT endpoint per product:

```http
POST /api/v2/Products/ImportDefaultClassification
  ?TenantCode=my-tenant-001
  &BrandCode=MBR
  &UserName=classifier@mybrand.com
Authorization: Bearer <token>
Content-Type: multipart/form-data

File: <binary file content>
```

***

{% endstep %}

{% step %}

### Bulk Upload Classifications

If you have a large set of classification assignments to apply — for example, after receiving an updated HS code list from your customs broker — use the bulk upload endpoint to submit them all at once.

```http
POST /api/v2/Classifications/BulkUpload
Authorization: Bearer <token>
Content-Type: multipart/form-data

file: <binary file content>
```

**Example using curl:**

```bash
curl -X POST \
  "https://logistics-customscatalog-api.sandbox.eshopworld.com/api/v2/Classifications/BulkUpload" \
  -H "Authorization: Bearer <token>" \
  -F "file=@/path/to/classifications-bulk.csv"
```

Responses:

* `200 OK` — file uploaded to the SFTP staging folder for processing
* `400 Bad Request` — file format or content issue
* `500 Internal Server Error`

The file is uploaded to the SFTP settings folder and processed asynchronously. After uploading, monitor the classification report (Step 10) to confirm the data has been applied.

***

{% endstep %}

{% step %}

### Export and Report

#### Export a Single Classification Group as CSV

Download a CSV file containing all products and region data associated with a specific classification:

```http
GET /api/v2/Classifications/ExportClassification/{classificationId}
Authorization: Bearer <token>
```

**Example:**

```http
GET /api/v2/Classifications/ExportClassification/clf-4201
Authorization: Bearer <token>
```

This returns a downloadable CSV. Useful for review, auditing, or sharing classification data with a customs team.

#### Download a Full Classifications Report by Brand

To get a complete picture of all classifications for a given brand or tenant:

```http
GET /api/v2/Classifications/Report?tenantCode=my-tenant-001
Authorization: Bearer <token>
```

Omit `tenantCode` to get the report across all brands.

#### Browse All Classifications (Paginated)

If you need to programmatically iterate all classifications without filtering by hierarchy:

http

```http
GET /api/v2/Classifications?PageNumber=1&PageSize=100&RegionCode=EU
Authorization: Bearer <token>
```

{% endstep %}
{% endstepper %}

***

### End-to-End Flow Summary

{% code expandable="true" %}

```ruby
Identify product category
        │
        ▼
GET /Classifications/Chapters
  → note chapterCode (e.g. "APPAREL")
        │
        ▼
GET /Classifications/Section?chapterCode=APPAREL
  → note sectionCode (e.g. "LEATHER")
        │
        ▼
GET /Classifications/SubSection?chapterCode=APPAREL&sectionCode=LEATHER
  → note subSectionCode (e.g. "WALLETS")
        │
        ▼
GET /Classifications/ChapterClassifications
    ?ChapterCode=APPAREL&SectionCode=LEATHER&SubSectionCode=WALLETS
  → note classificationId + classificationCode
        │
        ▼
GET /Classifications/{classificationId}/Regions
  → review duty and VAT rates by region
        │
        ├── Rates correct?
        │     └── No → POST /Classifications/regions/save with updated rates
        │
        └── Products using default classification?
              ├── Few products → PUT /Products/DefaultClassification
              ├── Many products → POST /Products/ImportDefaultClassification (file)
              └── Large HS code update → POST /Classifications/BulkUpload
                        │
                        ▼
              GET /Classifications/Report  ← verify result
```

{% endcode %}

***

### Common Issues

| Symptom                                                             | Likely Cause                                          | Action                                                                                                      |
| ------------------------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `GET /Chapters` returns empty                                       | No chapters configured in your environment            | Contact your eShopWorld integration team                                                                    |
| `ChapterClassifications` returns no results for a region            | Classification not configured for that region         | Check that `regionCode` is a supported value; use `/Classifications/{id}/Regions` to see what regions exist |
| Products remain in `DefaultClassifiedProducts` after classification | `defaultClassification` flag not cleared              | Run `PUT /Products/DefaultClassification` with `defaultClassification: false`                               |
| Bulk upload returns `400`                                           | File format not accepted                              | Verify the file structure matches the expected template; check column headers and encoding                  |
| `POST /Classifications/regions/save` returns `400`                  | Missing or invalid `classificationId` or `regionCode` | Confirm the `classificationId` exists using `GET /Classifications/ChapterClassifications` before saving     |
| `ExportClassification` returns `400`                                | Invalid `classificationId`                            | Verify the ID by querying `ChapterClassifications` first                                                    |


---

# 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/tutorials/classifying-products.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.
