> 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/page-1.md).

# Page 1

## eShopWorld Customs Catalog API — Developer Cookbook

> **Version:** 2.0 · **Base URL:** `https://logistics-customscatalog-api.sandbox.eshopworld.com`

This cookbook is organised by what you want to *achieve*, not by endpoint. Each recipe is self-contained: copy the code, swap in your values, and ship.

***

### Table of Contents

1. Before You Start — Authentication & Setup
2. Upload Your Product Catalog for the First Time
3. Look Up Products with Duty, Tax & Classification Data
4. Add Individual Products in Real-Time
5. Diagnose and Fix Invalid Products
6. Trigger a Catalog Reload
7. Query and Configure VAT Rates
8. Browse and Assign Classifications
9. Fetch Your Brand Configuration
10. Common Errors Reference

***

### 1. Before You Start — Authentication & Setup

#### Why

Every call to the Customs Catalog API requires a JWT bearer token. This section shows you how to attach it correctly and validates that your environment is working before you write a single line of business logic.

#### Ingredients

* A valid JWT token issued for your eShopWorld account
* Your **brand code** — a unique 3-letter identifier assigned to your retailer
* Your **tenant code** if you operate under a multi-tenant setup
* An HTTP client of your choice

#### Method

1. Obtain your JWT bearer token from your eShopWorld account team or identity provider.
2. Add the token to every request as an `Authorization` header.
3. Validate your setup with a lightweight health-check call.

#### Code

{% tabs %}
{% tab title="Python" %}
{% code expandable="true" %}

```python
import requests

BASE_URL = "https://logistics-customscatalog-api.sandbox.eshopworld.com"
TOKEN = "YOUR_JWT_TOKEN_HERE"

# Reuse this header dict across all requests
headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

# Smoke test — fetch total product counts to confirm auth works
response = requests.get(f"{BASE_URL}/api/v2/Home/TotalInvalidProducts", headers=headers)

if response.status_code == 200:
    print("✓ Auth working. Invalid product count:", response.json())
elif response.status_code == 401:
    print("✗ Unauthorized — check your JWT token")
else:
    print(f"✗ Unexpected status: {response.status_code}", response.text)
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript" %}
{% code expandable="true" %}

```javascript
const BASE_URL = "https://logistics-customscatalog-api.sandbox.eshopworld.com";
const TOKEN = "YOUR_JWT_TOKEN_HERE";

// Reuse these headers across all fetch() calls
const headers = {
  "Authorization": `Bearer ${TOKEN}`,
  "Content-Type": "application/json",
  "Accept": "application/json",
};

// Smoke test
const res = await fetch(`${BASE_URL}/api/v2/Home/TotalInvalidProducts`, { headers });

if (res.ok) {
  const data = await res.json();
  console.log("✓ Auth working. Invalid product count:", data);
} else if (res.status === 401) {
  console.error("✗ Unauthorized — check your JWT token");
} else {
  console.error(`✗ Unexpected status: ${res.status}`);
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Expected Output

```json
0
```

A `0` or any integer confirms authentication is working. A `401` means your token is missing, expired, or malformed.

***

### 2. Upload Your Product Catalog for the First Time

#### Why

This is the primary ingestion recipe. When you onboard to eShopWorld or need to push a new batch of products through the validation pipeline, you `POST` an array of product objects to the RetailerCatalog endpoint. The platform validates each product, classifies it for customs, and calculates duty and tax. The response is asynchronous.

#### Ingredients

* Your bearer token
* An array of product objects
* Required per product: `productCode`, `name`, `description`, `material`, `countryOfOrigin`
* Optional but strongly recommended: `hsCode`, `category`, `unitPrice`, `url`

> **Heads up on implicit prerequisites:** Shipping to Japan or Russia requires `url`. Specifying `weight` also requires `weightUnit`. Specifying `unitPrice.amount` also requires `unitPrice.currency`.

#### Method

1. Build your product array.
2. `POST` to `/api/v2/RetailerCatalog`.
3. Receive a `202 Accepted` while the job is processed asynchronously.

#### Code

{% tabs %}
{% tab title="Python" %}
{% code expandable="true" %}

```python
import requests

BASE_URL = "https://logistics-customscatalog-api.sandbox.eshopworld.com"
headers = {
    "Authorization": "Bearer YOUR_JWT_TOKEN_HERE",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

products = [
    {
        # --- Required fields ---
        "productCode": "SKU-001",
        "name": "Classic Wool Sweater",
        "description": "Men's knitted wool pullover sweater",
        "material": "100% Merino Wool",
        "countryOfOrigin": "IT",

        # --- Strongly recommended ---
        "hsCode": "611020",
        "category": "ApparelAccessories",
        "gender": "Male",
        "ageGroup": "Adult",
        "url": "https://yourstore.com/products/wool-sweater",
        "imageUrl": "https://yourstore.com/images/wool-sweater.jpg",
        "unitPrice": {
            "amount": 129.99,
            "currency": "EUR"
        },

        # --- Optional ---
        "weight": 0.6,
        "weightUnit": "Kg",
        "parentProductCode": "SKU-SWEATER-GROUP",
        "variantProductCode": "SKU-001-ALT",
        "dangerousGoods": False,
        "isCustomized": False,
        "isSubscription": False,
    },
    {
        "productCode": "SKU-002",
        "name": "Silk Scarf",
        "description": "Lightweight printed silk scarf",
        "material": "100% Silk",
        "countryOfOrigin": "CN",
    }
]

response = requests.post(
    f"{BASE_URL}/api/v2/RetailerCatalog",
    json=products,
    headers=headers
)

if response.status_code == 202:
    print("✓ Catalog accepted and queued for processing.")
    print("  Monitor /api/v2/Home/TotalInvalidProducts to check for errors.")
elif response.status_code == 400:
    print("✗ Validation error:", response.json())
else:
    print(f"✗ Error {response.status_code}:", response.text)
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript" %}
{% code expandable="true" %}

```javascript
const products = [
  {
    productCode: "SKU-001",
    name: "Classic Wool Sweater",
    description: "Men's knitted wool pullover sweater",
    material: "100% Merino Wool",
    countryOfOrigin: "IT",

    // Strongly recommended
    hsCode: "611020",
    category: "ApparelAccessories",
    gender: "Male",
    ageGroup: "Adult",
    url: "https://yourstore.com/products/wool-sweater",
    unitPrice: { amount: 129.99, currency: "EUR" },
  },
];

const res = await fetch(`${BASE_URL}/api/v2/RetailerCatalog`, {
  method: "POST",
  headers,
  body: JSON.stringify(products),
});

if (res.status === 202) {
  console.log("✓ Catalog queued for processing.");
} else {
  const err = await res.json();
  console.error("✗ Error:", err);
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Expected Output

```bash
HTTP 202 Accepted
(empty body)
```

#### Troubleshooting

| Status | Reason                                                                                       | Fix                                          |
| ------ | -------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `400`  | Missing required field (`productCode`, `name`, `description`, `material`, `countryOfOrigin`) | Check the error body for the offending field |
| `400`  | `countryOfOrigin` not a valid ISO code                                                       | Use a 2-letter ISO 3166-1 alpha-2 code       |
| `400`  | `weight` provided without `weightUnit`                                                       | Always pair `weight` and `weightUnit`        |
| `401`  | Token missing or expired                                                                     | Refresh your JWT and retry                   |

***

### 3. Look Up Products with Duty, Tax & Classification Data

#### Why

Once your catalog has been processed, this is the most commonly called recipe. Retrieve enriched product data including HS classification codes, duties, and tax rates for one or more SKUs, optionally filtered by delivery country.

#### Ingredients

* Your bearer token
* `BrandCode`
* One or more `ProductCode` values
* `DeliveryCountry`
* `CountryOfOrigin`

#### Method

1. Call `GET /api/v2/Products` with query parameters.
2. Pass multiple SKUs by repeating the `ProductCode` parameter.
3. Parse the enriched `ProductCatalogDto` response.

#### Code

{% tabs %}
{% tab title="Python" %}
{% code expandable="true" %}

```python
import requests

BASE_URL = "https://logistics-customscatalog-api.sandbox.eshopworld.com"
headers = {
    "Authorization": "Bearer YOUR_JWT_TOKEN_HERE",
    "Accept": "application/json",
}

params = {
    "BrandCode": "ABC",
    "ProductCode": ["SKU-001", "SKU-002"],
    "DeliveryCountry": ["DE", "FR"],
}

response = requests.get(
    f"{BASE_URL}/api/v2/Products",
    params=params,
    headers=headers
)

if response.status_code == 200:
    products = response.json()
    for product in products:
        print(f"SKU: {product['productCode']}")
        print(f"  Classification: {product.get('classificationCode')}")
        print(f"  HS Code:        {product.get('hsCode')}")
        print(f"  Category:       {product.get('categoryDesc')}")
        for cls in product.get('classifications', []):
            print(f"  Region: {cls.get('region')} | Duty: {cls.get('dutyRate')}% | VAT: {cls.get('vatRate')}%")
elif response.status_code == 404:
    print("✗ No products found for that BrandCode / ProductCode combination.")
elif response.status_code == 429:
    print("✗ Rate limited — back off and retry after a delay.")
else:
    print(f"✗ Error {response.status_code}:", response.text)
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript" %}
{% code expandable="true" %}

```javascript
const params = new URLSearchParams();
params.append("BrandCode", "ABC");
params.append("ProductCode", "SKU-001");
params.append("ProductCode", "SKU-002");
params.append("DeliveryCountry", "DE");
params.append("DeliveryCountry", "FR");

const res = await fetch(`${BASE_URL}/api/v2/Products?${params}`, { headers });

if (res.ok) {
  const products = await res.json();
  products.forEach(p => {
    console.log(`SKU: ${p.productCode}`);
    console.log(`  Classification: ${p.classificationCode}`);
    console.log(`  HS Code: ${p.hsCode}`);
  });
} else if (res.status === 429) {
  console.error("Rate limited — wait and retry.");
} else {
  console.error(`Error ${res.status}`);
}
```

{% endcode %}
{% endtab %}

{% tab title="cURL" %}
{% code expandable="true" %}

```bash
curl -G "https://logistics-customscatalog-api.sandbox.eshopworld.com/api/v2/Products" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN_HERE" \
  -H "Accept: application/json" \
  --data-urlencode "BrandCode=ABC" \
  --data-urlencode "ProductCode=SKU-001" \
  --data-urlencode "ProductCode=SKU-002" \
  --data-urlencode "DeliveryCountry=DE"
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Expected Output

{% code expandable="true" %}

```json
[
  {
    "productCode": "SKU-001",
    "name": "Classic Wool Sweater",
    "description": "Men's knitted wool pullover sweater",
    "material": "100% Merino Wool",
    "countryOfOrigin": "IT",
    "hsCode": "611020",
    "hsCodeRegion": "EU",
    "category": "ApparelAccessories",
    "categoryDesc": "Apparel & Accessories",
    "gender": "Male",
    "ageGroup": "Adult",
    "classificationCode": "CLS-4521",
    "classifications": [
      {
        "classificationId": "abc-123",
        "classificationCode": "CLS-4521",
        "classificationDesc": "Section XI > Chapter 61 > Knitted Apparel"
      }
    ],
    "unitPrice": { "amount": 129.99, "currency": "EUR" },
    "dangerousGoods": false,
    "isRestricted": false
  }
]
```

{% endcode %}

#### Troubleshooting

| Status | Reason                                               | Fix                                                         |
| ------ | ---------------------------------------------------- | ----------------------------------------------------------- |
| `404`  | `BrandCode` doesn't exist or `ProductCode` not found | Confirm the product was uploaded and processed successfully |
| `429`  | Too many requests                                    | Implement exponential backoff and batch your SKUs           |
| `400`  | Missing `BrandCode` or `ProductCode`                 | Both parameters are required                                |

***

### 4. Add Individual Products in Real-Time

#### Why

Batch catalog uploads cover your existing inventory, but not brand-new products going live today. This recipe shows you how to create one or more products and get back enriched classification, duty, and tax data synchronously.

#### Ingredients

* Your bearer token
* `brandCode`
* `deliveryCountry`
* All required product fields

#### Method

1. Build your product array with `brandCode` and `deliveryCountry`.
2. `POST` to `/api/v2/Products`.
3. Parse the synchronous `201 Created` response.

#### Code

{% tabs %}
{% tab title="Python" %}
{% code expandable="true" %}

```python
import requests

BASE_URL = "https://logistics-customscatalog-api.sandbox.eshopworld.com"
headers = {
    "Authorization": "Bearer YOUR_JWT_TOKEN_HERE",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

new_products = [
    {
        "productCode": "SKU-NEW-001",
        "name": "Leather Tote Bag",
        "description": "Full-grain leather tote bag with zip closure",
        "material": "Full-grain cowhide leather",
        "countryOfOrigin": "ES",
        "brandCode": "ABC",
        "deliveryCountry": "US",
        "hsCode": "420222",
        "category": "LuggageBags",
        "unitPrice": { "amount": 295.00, "currency": "USD" },
        "url": "https://yourstore.com/products/leather-tote",
    }
]

response = requests.post(
    f"{BASE_URL}/api/v2/Products",
    json=new_products,
    headers=headers
)

if response.status_code == 201:
    enriched = response.json()
    for product in enriched:
        print(f"Created: {product['productCode']}")
        print(f"  HS Code: {product.get('hsCode')}")
        print(f"  Classification: {product.get('classificationCode')}")
else:
    print(f"✗ Error {response.status_code}:", response.json())
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript" %}
{% code expandable="true" %}

```javascript
const newProducts = [
  {
    productCode: "SKU-NEW-001",
    name: "Leather Tote Bag",
    description: "Full-grain leather tote bag with zip closure",
    material: "Full-grain cowhide leather",
    countryOfOrigin: "ES",
    brandCode: "ABC",
    deliveryCountry: "US",
    hsCode: "420222",
    category: "LuggageBags",
    unitPrice: { amount: 295.00, currency: "USD" },
  },
];

const res = await fetch(`${BASE_URL}/api/v2/Products`, {
  method: "POST",
  headers,
  body: JSON.stringify(newProducts),
});

if (res.status === 201) {
  const enriched = await res.json();
  enriched.forEach(p => console.log(`Created: ${p.productCode}, HS: ${p.hsCode}`));
} else {
  const err = await res.json();
  console.error("Error:", err);
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Expected Output

{% code expandable="true" %}

```json
[
  {
    "productCode": "SKU-NEW-001",
    "name": "Leather Tote Bag",
    "hsCode": "420222",
    "hsCodeRegion": "US",
    "classificationCode": "CLS-7801",
    "classifications": [
      {
        "classificationId": "xyz-456",
        "classificationCode": "CLS-7801",
        "classificationDesc": "Section VIII > Chapter 42 > Handbags & Totes"
      }
    ],
    "unitPrice": { "amount": 295.00, "currency": "USD" },
    "isRestricted": false
  }
]
```

{% endcode %}

#### Troubleshooting

| Status                            | Reason                                         | Fix                                                    |
| --------------------------------- | ---------------------------------------------- | ------------------------------------------------------ |
| `400`                             | Missing required field                         | Check the error body for the missing field             |
| `400`                             | Invalid `countryOfOrigin` or `deliveryCountry` | Use valid ISO 3166-1 alpha-2 codes                     |
| `201` but no `classificationCode` | Product couldn't be auto-classified            | Provide an `hsCode` to improve classification accuracy |

***

### 5. Diagnose and Fix Invalid Products

#### Why

After a catalog upload, some products may fail validation. This recipe walks you through the remediation loop: list the failures, understand the error, fix the source data, and trigger reprocessing.

#### Ingredients

* Your bearer token
* Your `brandCode`
* Corrected product data ready to re-upload

#### Method

1. List invalid products.
2. Diagnose the error message on each product.
3. Fix your source data.
4. Re-upload the corrected products.
5. Trigger reprocessing.

#### Code

{% tabs %}
{% tab title="Python" %}
**Step 1: List invalid products**

{% code expandable="true" %}

```python
import requests

BASE_URL = "https://logistics-customscatalog-api.sandbox.eshopworld.com"
headers = {
    "Authorization": "Bearer YOUR_JWT_TOKEN_HERE",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

payload = {
    "brandCode": "ABC",
    "pageNumber": 1,
    "pageSize": 50,
}

response = requests.post(
    f"{BASE_URL}/api/v2/InvalidProducts/Invalid",
    json=payload,
    headers=headers
)

if response.status_code == 200:
    result = response.json()
    items = result.get("items", [])
    print(f"Found {len(items)} invalid product(s):\n")
    for product in items:
        print(f"  SKU: {product.get('productCode')}")
        print(f"  Error: {product.get('errorMessage')}")
        print(f"  Error Code: {product.get('errorCode')}")
        print()
else:
    print(f"✗ Error {response.status_code}:", response.json())
```

{% endcode %}

**Step 2: Trigger reprocessing**

{% code expandable="true" %}

```python
reprocess_response = requests.post(
    f"{BASE_URL}/api/v2/InvalidProducts/Reprocess",
    params={"brandCode": "ABC"},
    headers=headers
)

if reprocess_response.status_code == 202:
    print("✓ Reprocessing job queued for brand ABC.")
else:
    print(f"✗ Error {reprocess_response.status_code}:", reprocess_response.json())
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript" %}
**Full remediation loop**

{% code expandable="true" %}

```javascript
const listRes = await fetch(`${BASE_URL}/api/v2/InvalidProducts/Invalid`, {
  method: "POST",
  headers,
  body: JSON.stringify({ brandCode: "ABC", pageNumber: 1, pageSize: 50 }),
});

const { items } = await listRes.json();
console.log(`Found ${items.length} invalid product(s):`);
items.forEach(p => {
  console.log(`  SKU: ${p.productCode} | Error: ${p.errorMessage}`);
});

const reprocessRes = await fetch(
  `${BASE_URL}/api/v2/InvalidProducts/Reprocess?brandCode=ABC`,
  { method: "POST", headers }
);

if (reprocessRes.status === 202) {
  console.log("✓ Reprocessing queued.");
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Expected Output (List Invalid Products)

{% code expandable="true" %}

```json
{
  "items": [
    {
      "productCode": "SKU-BAD-001",
      "errorMessage": "CountryOfOrigin 'XX' is not a valid ISO country code.",
      "errorCode": 1022
    },
    {
      "productCode": "SKU-BAD-002",
      "errorMessage": "HSCode '1234' must be a minimum of 6 digits.",
      "errorCode": 1045
    }
  ],
  "continuationToken": null
}
```

{% endcode %}

#### Expected Output (Reprocess)

```bash
HTTP 202 Accepted
(empty body)
```

#### Troubleshooting

| Status                                       | Reason                                                   | Fix                                                               |
| -------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------- |
| `500` on list                                | Internal error fetching invalid products                 | Check that `brandCode` is correct and retry                       |
| `400` on reprocess                           | `brandCode` not found or no invalid products to process  | Confirm brand code and check `TotalInvalidProducts` first         |
| Reprocess returns `202` but problems persist | The underlying data wasn't corrected before reprocessing | Fix and re-upload the corrected products before calling reprocess |

***

### 6. Trigger a Catalog Reload

#### Why

If your catalog data has been updated in your source system, you can instruct the platform to re-pull and revalidate your master catalog without doing a full re-upload.

#### Ingredients

* Your bearer token
* Your `brandCode` or `tenantCode`

#### Method

1. `POST` to `/api/v2/MasterCatalog/Reload`.
2. Receive a `201 Created` with a queue record.
3. Monitor for invalid products as normal.

#### Code

{% tabs %}
{% tab title="Python" %}
{% code expandable="true" %}

```python
import requests

BASE_URL = "https://logistics-customscatalog-api.sandbox.eshopworld.com"
headers = {
    "Authorization": "Bearer YOUR_JWT_TOKEN_HERE",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

payload = {
    "brandCode": "ABC",
    # "tenantCode": "TENANT1"
}

response = requests.post(
    f"{BASE_URL}/api/v2/MasterCatalog/Reload",
    json=payload,
    headers=headers
)

if response.status_code == 201:
    queue = response.json()
    print(f"✓ Catalog reload queued.")
    print(f"  Job ID:        {queue.get('id')}")
    print(f"  Brand:         {queue.get('brandCode')}")
    print(f"  Requested at:  {queue.get('requestDate')}")
    print(f"  Correlation ID:{queue.get('correlationId')}")
elif response.status_code == 412:
    print("✗ Precondition Failed — a reload may already be in progress for this brand.")
elif response.status_code == 400:
    print("✗ Bad Request:", response.json())
```

{% endcode %}
{% endtab %}

{% tab title="cURL" %}
{% code expandable="true" %}

```bash
curl -X POST \
  "https://logistics-customscatalog-api.sandbox.eshopworld.com/api/v2/MasterCatalog/Reload" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"brandCode": "ABC"}'
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Expected Output

```json
{
  "id": "8f3c2a19-0001-4b2e-9d7a-abc000000001",
  "brandCode": "ABC",
  "requestDate": "2025-06-15T10:32:00Z",
  "correlationId": "corr-1234-5678",
  "modifiedBy": "api-user@yourcompany.com"
}
```

#### Troubleshooting

| Status                    | Reason                                                   | Fix                                                            |
| ------------------------- | -------------------------------------------------------- | -------------------------------------------------------------- |
| `412 Precondition Failed` | A reload job is already queued or running for this brand | Wait for the current job to complete before triggering another |
| `400`                     | `brandCode` is missing or unrecognised                   | Confirm your brand code                                        |

***

### 7. Query and Configure VAT Rates

#### Why

If you manage VAT rates on behalf of your brand, this recipe shows you how to list existing rates, add a new rate, and update an existing one.

#### Ingredients

* Your bearer token
* `VatBandId`
* `RegionCode` and `CountryIso`
* A `VatRateDto` object for `POST` and `PUT`

#### Code

{% tabs %}
{% tab title="Query VAT rates" %}
{% code expandable="true" %}

```python
import requests

BASE_URL = "https://logistics-customscatalog-api.sandbox.eshopworld.com"
headers = {
    "Authorization": "Bearer YOUR_JWT_TOKEN_HERE",
    "Accept": "application/json",
}

params = {
    "PageNumber": 1,
    "PageSize": 25,
    "RegionCode": "EU",
    "CountryIso": "DE",
    "OrderByField": "countryIso",
    "IsOrderByAsc": True,
}

response = requests.get(f"{BASE_URL}/api/v2/Vat", params=params, headers=headers)

if response.status_code == 200:
    result = response.json()
    print(f"Total VAT rates: {result.get('totalCount')}")
    for vat in result.get("items", []):
        print(f"  Country: {vat.get('countryIso')} | Rate: {vat.get('vatRate')}% | Band: {vat.get('vatBandId')}")
else:
    print(f"✗ Error {response.status_code}:", response.json())
```

{% endcode %}
{% endtab %}

{% tab title="Add a VAT rate" %}
{% code expandable="true" %}

```python
new_vat = {
    "vatBandId": 3,
    "regionCode": "EU",
    "countryIso": "PT",
    "vatRate": 23.0,
}

response = requests.post(
    f"{BASE_URL}/api/v2/Vat",
    json=new_vat,
    headers={**headers, "Content-Type": "application/json"}
)

if response.status_code == 201:
    created = response.json()
    print(f"✓ VAT rate created with ID: {created.get('id')}")
else:
    print(f"✗ Error {response.status_code}:", response.json())
```

{% endcode %}
{% endtab %}

{% tab title="Update a VAT rate" %}
{% code expandable="true" %}

```python
vat_id = "EXISTING_VAT_ID"

updated_vat = {
    "vatBandId": 3,
    "regionCode": "EU",
    "countryIso": "PT",
    "vatRate": 23.5,
}

response = requests.put(
    f"{BASE_URL}/api/v2/Vat/{vat_id}",
    json=updated_vat,
    headers={**headers, "Content-Type": "application/json"}
)

if response.status_code == 200:
    print(f"✓ VAT rate {vat_id} updated.")
elif response.status_code == 404:
    print("✗ VAT rate not found — check the ID.")
else:
    print(f"✗ Error {response.status_code}:", response.json())
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Expected Output (GET)

{% code expandable="true" %}

```json
{
  "totalCount": 1,
  "pageNumber": 1,
  "pageSize": 25,
  "items": [
    {
      "id": "vat-de-001",
      "vatBandId": 2,
      "regionCode": "EU",
      "countryIso": "DE",
      "vatRate": 19.0
    }
  ]
}
```

{% endcode %}

#### Troubleshooting

| Status       | Reason                             | Fix                                          |
| ------------ | ---------------------------------- | -------------------------------------------- |
| `400` on GET | Missing `PageNumber` or `PageSize` | These are required for every `GET /Vat` call |
| `404` on PUT | VAT record not found               | Retrieve the correct ID first                |
| `500`        | Server error                       | Retry and contact support if it persists     |

***

### 8. Browse and Assign Classifications

#### Why

The platform maps products to customs classifications. If you need to validate HS code mapping, build a classification picker, or audit coverage, this recipe shows you how to browse them.

#### Ingredients

* Your bearer token
* `RegionCode`
* `PageNumber` and `PageSize`

#### Code

{% tabs %}
{% tab title="List classifications" %}
{% code expandable="true" %}

```python
import requests

BASE_URL = "https://logistics-customscatalog-api.sandbox.eshopworld.com"
headers = {
    "Authorization": "Bearer YOUR_JWT_TOKEN_HERE",
    "Accept": "application/json",
}

params = {
    "PageNumber": 1,
    "PageSize": 50,
    "RegionCode": "EU",
    "OrderByField": "classificationCode",
    "IsOrderByAsc": True,
}

response = requests.get(
    f"{BASE_URL}/api/v2/Classifications",
    params=params,
    headers=headers
)

if response.status_code == 200:
    result = response.json()
    print(f"Total classifications available: {result.get('totalCount')}\n")
    for cls in result.get("items", []):
        print(f"  ID:   {cls.get('classificationId')}")
        print(f"  Code: {cls.get('classificationCode')}")
        print(f"  Desc: {cls.get('classificationDesc')}")
        print()
else:
    print(f"✗ Error {response.status_code}:", response.json())
```

{% endcode %}
{% endtab %}

{% tab title="Browse the hierarchy" %}
{% code expandable="true" %}

```python
chapters = requests.get(
    f"{BASE_URL}/api/v2/Classifications/Chapters",
    headers=headers
).json()

sections = requests.get(
    f"{BASE_URL}/api/v2/Classifications/Section",
    params={"chapterCode": "61"},
    headers=headers
).json()

subsections = requests.get(
    f"{BASE_URL}/api/v2/Classifications/SubSection",
    params={"chapterCode": "61", "sectionCode": "6110"},
    headers=headers
).json()
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Expected Output

{% code expandable="true" %}

```json
{
  "totalCount": 312,
  "pageNumber": 1,
  "pageSize": 50,
  "items": [
    {
      "classificationId": "cls-001",
      "classificationCode": "6110.20",
      "classificationDesc": "Section XI > Chapter 61 > Jerseys and Pullovers — Wool"
    },
    {
      "classificationId": "cls-002",
      "classificationCode": "4202.22",
      "classificationDesc": "Section VIII > Chapter 42 > Handbags — Leather"
    }
  ]
}
```

{% endcode %}

***

### 9. Fetch Your Brand Configuration

#### Why

Before building any integration, it helps to understand how your brand is configured on the platform.

#### Ingredients

* Your bearer token
* Your `brandCode`

#### Code

{% tabs %}
{% tab title="Python" %}
{% code expandable="true" %}

```python
import requests

BASE_URL = "https://logistics-customscatalog-api.sandbox.eshopworld.com"
headers = {
    "Authorization": "Bearer YOUR_JWT_TOKEN_HERE",
    "Accept": "application/json",
}

brand_code = "ABC"

response = requests.get(
    f"{BASE_URL}/api/v2/BrandConfig/{brand_code}",
    headers=headers
)

if response.status_code == 200:
    config = response.json()
    print("Brand Configuration:")
    print(f"  Brand Code:  {config.get('brandCode')}")
    print(f"  Active:      {config.get('isActive')}")
    print(f"  Regions:     {config.get('regions')}")
    print(f"  Settings:    {config.get('settings')}")
elif response.status_code == 404:
    print(f"✗ Brand '{brand_code}' not found. Verify with your eShopWorld contact.")
elif response.status_code == 500:
    print("✗ Server error — retry or raise with eShopWorld support.")
```

{% endcode %}
{% endtab %}

{% tab title="cURL" %}
{% code expandable="true" %}

```bash
curl "https://logistics-customscatalog-api.sandbox.eshopworld.com/api/v2/BrandConfig/ABC" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN_HERE" \
  -H "Accept: application/json"
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Expected Output

```json
{
  "brandCode": "ABC",
  "isActive": true,
  "regions": ["EU", "US", "APAC"],
  "settings": {
    "defaultCurrency": "EUR",
    "autoClassify": true
  }
}
```

#### Troubleshooting

| Status | Reason                                   | Fix                                               |
| ------ | ---------------------------------------- | ------------------------------------------------- |
| `404`  | Brand code doesn't exist in the platform | Confirm your brand code with your account manager |
| `500`  | Internal server error                    | Retry with exponential backoff                    |

***

### 10. Common Errors Reference

This table covers the error codes and status codes you are most likely to encounter across all recipes.

| HTTP Status                   | When It Occurs                                               | What To Do                                                                    |
| ----------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| `400 Bad Request`             | Missing required fields, invalid enum values, malformed JSON | Read the error body for `message` and `code`                                  |
| `401 Unauthorized`            | Missing or expired JWT                                       | Refresh your token and retry                                                  |
| `404 Not Found`               | Resource doesn't exist                                       | Confirm the identifier before querying                                        |
| `412 Precondition Failed`     | Operation not permitted in the current state                 | Wait for the in-progress operation to complete                                |
| `429 Too Many Requests`       | Rate limit exceeded                                          | Implement exponential backoff and batch requests                              |
| `500 Internal Server Error`   | Platform-side error                                          | Retry once, then raise with support if it persists                            |
| `202 Accepted` (not an error) | Async job queued                                             | This is expected. Poll `/api/v2/Home/TotalInvalidProducts` to monitor outcome |

#### Reading the Error Object

```json
[
  {
    "code": 1022,
    "message": "CountryOfOrigin 'XX' is not a valid ISO country code."
  }
]
```

Always log both `code` and `message`. The `code` is stable across API versions and is safe to handle programmatically.


---

# 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/page-1.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.
