> 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/uploading-a-catalog.md).

# Uploading a Catalog

## Retailer Catalog

Allows a retailer to upload a catalog that will be subject to validation processing.

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

## Prerequisites

* A valid Bearer JWT token. Refer to [Authentication](/getting-started/get-started-with-esw/readme/quickstart.md)
* Product data ready to submit
  {% endhint %}

{% tabs fullWidth="true" %}
{% tab title="Code Walkthrough" %}
{% @code-walkthrough/code-walkthrough title="Upload a Catalog" language="json" filename="Request Sample" code="	curl -L \
\--url '<https://logistics-customscatalog-api.sandbox.eshopworld.com/api/v2/RetailerCatalog>' \
\--header 'Authorization: Bearer YOUR\_SECRET\_TOKEN' \
\--header 'Content-Type: application/json-patch+json' \
\--header 'Accept: */*' \
\--data '\[
{
"productCode": "text",
"name": "text",
"description": "text",
"material": "text",
"countryOfOrigin": "AD",
"hsCode": "text",
"hsCodeRegion": "AD",
"parentProductCode": "text",
"category": "AnimalsPetSupplies",
"gender": "Male",
"ageGroup": "Newborn",
"size": "text",
"weight": 1,
"weightUnit": "Lb",
"url": "text",
"imageUrl": "text",
"unitPrice": {
"amount": 1,
"currency": "ARS"
},
"dangerousGoods": true,
"isCustomized": true,
"additionalProductCode": "text",
"variantProductCode": "text",
"restrictions": \[
{
"type": "hsCodeRestricted",
"criteria": "exclude",
"countries": \[
"text"
]
}
],
"isRestricted": true,
"mid": "text",
"midAddressZip": "text",
"additionalInformation": "text",
"categoryInformation": "text",
"isSubscription": true,
"eccn": "text"
}
]'" steps="\[
{"label":"Product identifiers","description":"<code>productCode</code> is the primary SKU — the API uses it to match and upsert. <code>parentProductCode</code> links a variant to its parent. <code>variantProductCode</code> and <code>additionalProductCode</code> are secondary references.","lines":"8-9,15,30-31","tip":"Changing productCode creates a duplicate rather than updating the existing product"},
{"label":"Product content","description":"<code>name</code> and <code>description</code> are shopper-facing. <code>material</code> feeds into customs declarations and may be required for certain HS codes. <code>url</code> is the product page and <code>imageUrl</code> appears in fulfilment and returns documentation.","lines":"9-11,22-23","tip":"Keep description factual — it may appear verbatim on customs paperwork"},
{"label":"Customs classification","description":"<code>hsCode</code> determines import duty rates. <code>hsCodeRegion</code> scopes it to a customs region — codes can differ between EU, US, and UK. <code>countryOfOrigin</code> is the ISO code for where the product was manufactured.","lines":"12-14","tip":"Verify hsCode against the official tariff schedule per region — an incorrect code causes mis-applied duty or customs holds"},
{"label":"Category and pricing","description":"<code>category</code> must exactly match an allowed enum value — it drives which pricing multiplier and duty rules are selected from the pricing config. <code>unitPrice</code> is the retailer's source price in their home currency — pass the clean price with zero embedded rates.","lines":"16,24-27","tip":"A category mismatch means the wrong multiplier is applied — cross-check against your pricing config's category IDs"},
{"label":"Physical attributes","description":"<code>weight</code> and <code>weightUnit</code> are used for carrier rate selection and customs paperwork. <code>dangerousGoods: true</code> triggers additional carrier handling requirements — carriers may refuse shipment if undeclared.","lines":"20-21,28","tip":"Use consistent weight units across all products — mixing Lb and Kg causes carrier billing errors"},
{"label":"Restrictions and compliance","description":"The <code>restrictions</code> array defines per-country shipping rules by <code>type</code> and <code>criteria</code>. <code>isRestricted</code> is a top-level override. <code>mid</code> and <code>midAddressZip</code> are US Customs manufacturer identifiers. <code>eccn</code> is the Export Control Classification Number — required for controlled or dual-use goods.","lines":"32-43,47","tip":"Review restrictions whenever hsCode changes — a new code may carry different country-level rules"}
]" fullWidth="true" %}
{% endtab %}
{% endtabs %}

***

{% stepper %}
{% step %}

### Prepare Your Product Data

Each product in the catalog is represented by a `RetailerCatalogProduct` object. The table below describes every available field

{% tabs %}
{% tab title="Required" %}

| Field             | Type           | Description                                                  |
| ----------------- | -------------- | ------------------------------------------------------------ |
| `productCode`     | string         | Your unique master product identifier or SKU                 |
| `name`            | string         | The product name                                             |
| `description`     | string         | A short description of the product                           |
| `material`        | string         | Description of what the product is made of                   |
| `countryOfOrigin` | string (ISO-2) | ISO 3166-1 alpha-2 country code, e.g. `"US"`, `"CN"`, `"DE"` |
| {% endtab %}      |                |                                                              |

{% tab title="Optional" %}

| Field                   | Type          | Description                                                                                                                                                                  |
| ----------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hsCode`                | string        | Your HS code for the product — minimum 6 digits                                                                                                                              |
| `hsCodeRegion`          | string (enum) | The region the HS code applies to, e.g. `"EU"`, `"US"`, `"CN"`                                                                                                               |
| `parentProductCode`     | string        | Parent SKU for variant grouping                                                                                                                                              |
| `variantProductCode`    | string        | An alternative version of your product SKU                                                                                                                                   |
| `additionalProductCode` | string        | Manufacturer's code or secondary SKU                                                                                                                                         |
| `category`              | string (enum) | eShopWorld product category                                                                                                                                                  |
| `gender`                | string (enum) | `"Male"`, `"Female"`, `"Unisex"`                                                                                                                                             |
| `ageGroup`              | string (enum) | e.g. `"Adult"`, `"Kids"`                                                                                                                                                     |
| `size`                  | string        | Product size                                                                                                                                                                 |
| `weight`                | number        | <p>Product weight</p><div data-gb-custom-block data-tag="hint" data-style="danger" class="hint hint-danger"><p>Required if <code>weightUnit</code> is set</p></div>          |
| `weightUnit`            | string (enum) | Unit for weight, e.g. `"kg"`, `"g"`, `"lb"`                                                                                                                                  |
| `url`                   | string        | <p>Product landing page URL</p><div data-gb-custom-block data-tag="hint" data-style="danger" class="hint hint-danger"><p>Required when shipping to Russia or Japan</p></div> |
| `imageUrl`              | string        | URL of the product image thumbnail                                                                                                                                           |
| `unitPrice`             | object        | Retail price object with amount and currency                                                                                                                                 |
| `dangerousGoods`        | boolean       | `true` if the product is classified as a hazardous material                                                                                                                  |
| `isCustomized`          | boolean       | `true` if the product has been customised from its original design                                                                                                           |
| `isRestricted`          | boolean       | `true` to globally restrict sale regardless of destination                                                                                                                   |
| `restrictions`          | array         | Per-country restrictions — array of `RestrictionDto` objects                                                                                                                 |
| `isSubscription`        | boolean       | `true` if this is a subscription-based product                                                                                                                               |
| `mid`                   | string        | Manufacturer ID                                                                                                                                                              |
| `midAddressZip`         | string        | Manufacturer's postal/ZIP code                                                                                                                                               |
| `eccn`                  | string        | Export Control Classification Number                                                                                                                                         |
| `additionalInformation` | string        | Any other free-text product information                                                                                                                                      |
| `categoryInformation`   | string        | Any additional category-specific information                                                                                                                                 |
| {% endtab %}            |               |                                                                                                                                                                              |
| {% endtabs %}           |               |                                                                                                                                                                              |

#### Category Values

<details>

<summary>The <code>category</code> field accepts one of the following strings</summary>

`AnimalsPetSupplies` · `ApparelAccessories` · `ArtsEntertainment` · `BabyToddler` · `BusinessIndustrial` · `CamerasOptics` · `Electronics` · `FoodBeveragesTobacco` · `Furniture` · `Hardware` · `HealthBeauty` · `HomeGarden` · `LuggageBags` · `Mature` · `Media` · `OfficeSupplies` · `ReligiousCeremonial` · `Software` · `SportingGoods` · `ToysGames` · `VehiclesParts`

</details>

#### Example Product Object

{% code expandable="true" %}

```json
{
  "productCode": "SKU-00123",
  "name": "Classic Leather Wallet",
  "description": "Slim bifold wallet in full-grain leather",
  "material": "Full-grain cowhide leather",
  "countryOfOrigin": "IT",
  "hsCode": "420232",
  "hsCodeRegion": "EU",
  "category": "ApparelAccessories",
  "gender": "Male",
  "weight": 0.08,
  "weightUnit": "kg",
  "url": "https://www.mybrand.com/products/classic-leather-wallet",
  "imageUrl": "https://cdn.mybrand.com/images/sku-00123.jpg",
  "unitPrice": {
    "amount": 49.99,
    "currency": "EUR"
  },
  "dangerousGoods": false,
  "isCustomized": false
}
```

{% endcode %}
{% endstep %}

{% step %}

### Choose Your Upload Method

There are **three** ways to submit a catalog depending on your setup.

{% hint style="info" %}

#### Option A — Upload by Brand Code (JSON)

{% endhint %}

Use this when you identify your brand with a `brandCode` and are submitting product data as JSON.

{% code expandable="true" %}

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

[
  { ...product1 },
  { ...product2 }
]
```

{% endcode %}

The response is `202 Accepted`, meaning the catalog has been queued for validation processing — not that validation has completed.

{% hint style="info" %}

#### Option B — Upload by Tenant Code (JSON)

{% endhint %}

Use this when you operate under a specific `tenantCode`. The tenant code is passed as a path parameter.

{% code expandable="true" %}

```http
POST /api/v2/RetailerCatalog/TenantCode/{tenantCode}
Authorization: Bearer <token>
Content-Type: application/json

[
  { ...product1 },
  { ...product2 }
]
```

{% endcode %}

**Example:**

{% code expandable="true" %}

```http
POST /api/v2/RetailerCatalog/TenantCode/my-tenant-001
Authorization: Bearer <token>
Content-Type: application/json

[
  {
    "productCode": "SKU-00123",
    "name": "Classic Leather Wallet",
    "description": "Slim bifold wallet in full-grain leather",
    "material": "Full-grain cowhide leather",
    "countryOfOrigin": "IT"
  }
]
```

{% endcode %}

{% hint style="info" %}

#### Option C — File Import (Multipart Upload)

{% endhint %}

Use this when submitting a catalog as a file rather than a JSON body. This is useful for bulk imports from a spreadsheet or existing catalog export.

{% code expandable="true" %}

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

File: <binary file content>
```

{% endcode %}

**Example using curl:**

{% code expandable="true" %}

```http
curl -L \
  --request POST \
  --url 'https://logistics-customscatalog-api.sandbox.eshopworld.com//api/v2/RetailerCatalog/TenantImport' \
  --header 'Authorization: YOUR_API_KEY' \
  --header 'Content-Type: multipart/form-data' \
  --form 'File=@/path/to/catalog.csv'
```

{% endcode %}
{% endstep %}

{% step %}

### Verify the Upload

After uploading, verify the platform has the latest version of your catalog by checking the master catalog timestamp.

```http
GET /api/v2/RetailerCatalog/GetMasterCatalogTimestamp/TenantCode/{tenantCode}
Authorization: Bearer <token>
```

This returns the timestamp of the most recently received master catalog for that tenant. Compare it against your upload time to confirm the data landed correctly before proceeding.

You can also download the raw master catalog file the platform has stored:

```http
GET /api/v2/RetailerCatalog/File/TenantCode/{tenantCode}
Authorization: Bearer <token>
```

{% hint style="info" %}
This is useful for auditing exactly what data the platform received and will validate against.
{% endhint %}
{% endstep %}

{% step %}

### Check for Validation Errors

The catalog upload is asynchronous — the platform validates your products in the background after accepting the request. Use the following endpoints to check the outcome.

#### 4a. Get a Quick Count

First, get a high-level count of how many products failed validation:

```http
GET /api/v2/Home/TotalInvalidProducts
Authorization: Bearer <token>
```

If the count is zero, your catalog passed validation and you are done. If not, proceed to 4b.

#### 4b. List Invalid Products

Fetch the details of invalid products to understand what failed:

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

{
  "pageNumber": 1,
  "pageSize": 50,
  "tenantCode": "my-tenant-001",
  "searchType": "InvalidProducts"
}
```

{% tabs %}
{% tab title="searchType" %}
{% hint style="info" icon="magnifying-glass-arrows-rotate" %}
The `searchType` field controls what category of errors you see.
{% endhint %}

{% code expandable="true" %}

```http
GET /api/v2/InvalidProducts/Invalid/File/{tenantCode}/{searchType}
Authorization: Bearer <token>
```

{% endcode %}

| Value                       | Description                                                           |
| --------------------------- | --------------------------------------------------------------------- |
| `InvalidProducts`           | Products that failed validation rules                                 |
| `DefaultClassifiedProducts` | Products assigned a default classification because no match was found |
| `IncompleteRatings`         | Products with incomplete duty/tax rating data                         |
| `FailedFilesAndApiRequests` | Upload failures at the file or request level                          |
| `AwaitingClassification`    | Products waiting to be classified                                     |

{% hint style="info" icon="magnifying-glass-arrows-rotate" %}
The `requestType` field can be used to narrow results further
{% endhint %}

{% code expandable="true" %}

```http
GET /api/v2/InvalidProducts/Invalid/File/{tenantCode}/{searchType}
Authorization: Bearer <token>
```

{% endcode %}

| Value                   | Description                                    |
| ----------------------- | ---------------------------------------------- |
| `ValidProducts`         | Products that passed validation                |
| `InvalidProducts`       | Products that failed validation                |
| `DefaultClassification` | Products with a default classification applied |
| `InvalidLogs`           | Error log entries                              |
| {% endtab %}            |                                                |
| {% endtabs %}           |                                                |

#### 4c. Download an Error Report

For a bulk view suitable for sharing or review, download an error file:

```http
GET /api/v2/InvalidProducts/Invalid/File/{tenantCode}/{searchType}
Authorization: Bearer <token>
```

**Example:**

```http
GET /api/v2/InvalidProducts/Invalid/File/my-tenant-001/InvalidProducts
Authorization: Bearer <token>
```

This returns a downloadable file (typically CSV) listing all invalid products with their error details.
{% endstep %}

{% step %}

### Fix and Reprocess

Once you have identified the validation errors, correct the affected products in your system and re-upload the catalog using the same method from Step 2.

Alternatively, if the products are already in the platform but need to be run through the classification pipeline again, trigger a reload without re-uploading:

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

{
  "tenantCode": "my-tenant-001"
}
```

Or by `brandCode`:

```json
{
  "brandCode": "MBR"
}
```

You can also trigger reprocessing for just the invalid products rather than the entire catalog:

```http
POST /api/v2/InvalidProducts/Reprocess?brandCode=MBR
Authorization: Bearer <token>
```

Or by tenant code:

```http
POST /api/v2/InvalidProducts/Reprocess/TenantCode?tenantCode=my-tenant-001
Authorization: Bearer <token>
```

After reprocessing, repeat Step 4 to confirm errors have been resolved.
{% endstep %}
{% endstepper %}

## End-to-End Flow Summary

{% code expandable="true" %}

```bash
1. Prepare product array
        │
        ▼
2. POST /RetailerCatalog  ──or──  POST /RetailerCatalog/TenantCode/{code}
   (202 Accepted)                  (202 Accepted)
        │
        ▼
3. GET /RetailerCatalog/GetMasterCatalogTimestamp/TenantCode/{code}
   → Confirm timestamp matches your upload
        │
        ▼
4. GET /Home/TotalInvalidProducts
   → 0?  ✅ Done
   → >0? Continue ↓
        │
        ▼
5. POST /InvalidProducts/Invalid
   → Review error details per product
        │
        ▼
6. Fix errors in source data
        │
        ├── Re-upload → go to step 2
        │
        └── Already in platform → POST /MasterCatalog/Reload
                                         │
                                         ▼
                                   Repeat from step 4
```

{% endcode %}

## Common Errors

| Scenario                                    | Likely Cause                                  | Resolution                                                                                              |
| ------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `400 Bad Request` on upload                 | Missing required fields or invalid enum value | Check `productCode`, `name`, `description`, `material`, `countryOfOrigin` are all present and non-empty |
| Products appear as `InvalidProducts`        | `countryOfOrigin` is not a valid ISO-2 code   | Use a valid ISO 3166-1 alpha-2 code                                                                     |
| Products appear as `InvalidProducts`        | `hsCode` is fewer than 6 digits               | Pad or correct the HS code                                                                              |
| Products appear as `AwaitingClassification` | No matching HS code or category found         | Provide `hsCode` and `category` to assist classification                                                |
| `url` field flagged as missing              | Product is being shipped to Russia or Japan   | The `url` field is mandatory for those destinations                                                     |
| `weight` without `weightUnit`               | Conditional field rule                        | Always provide both `weight` and `weightUnit` together                                                  |


---

# 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/uploading-a-catalog.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.
