Products: Endpoint

Endpoint

Products

Complete CRUD operations for product data.


Overview

OperationMethodEndpointScope
Export AllGET/xpanel/xoport/export/json/productsproducts:read
Single productGET/xpanel/xoport/export/json/product/{ID}products:read
ID RangeGET/xpanel/xoport/export/json/products/{START}:{END}products:read
ImportPOST/xpanel/xoport/import/json/productsproducts:write
UpdatePUT/xpanel/xoport/import/json/productsproducts:write
DeleteDELETE/xpanel/xoport/delete/json/productsproducts:delete

Export (GET)

Note: The product ID is passed as a path segment, not as a query parameter. Use /product/{ID} (singular) for a single product.

URL Patterns

URLDescription
/export/json/product/123Single product with ID 123
/export/json/productsAll products (paginated)
/export/json/products/100:200Products with IDs 100 through 200

Query Parameters

ParameterTypeDescription
limitintMax. number (Default: 50, Max: 250)
offsetintStart position for pagination

Example Requests

# Einzelprodukt abrufen
curl -X GET "https://shop.de/xpanel/xoport/export/json/product/123" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Alle Produkte paginiert
curl -X GET "https://shop.de/xpanel/xoport/export/json/products?limit=50&offset=0" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# ID-Range
curl -X GET "https://shop.de/xpanel/xoport/export/json/products/100:200" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response Fields

Each product contains:

  • products_id, products_model, products_ean
  • products_price, products_weight, products_quantity, products_cost
  • manufacturers_id, products_status
  • contract, contract_use_current_price, contract_option v2.5.0
  • products_inventory_management, products_min_qty, products_max_qty, products_qty_blocks, products_base_price v2.5.0
  • service, sitemap, non_cart, non_price, products_free_shipping v2.5.0
  • shipping_profile, products_unit_id, base_unit_id, products_sort_order v2.5.0
  • images[] - Product images with sorting
  • categories[] - Category IDs
  • groups[] - Customer group prices
  • customers_prices[] - Customer-specific prices
  • specials[] - Special prices
  • extra_fields[] - Additional fields
  • badges[] - Product badges
  • xsells[] - Cross-selling products
  • upsells[] - Shopping cart suggestions (“Goes well with this”) v2.18.0+
  • tabs[] - Product tabs
  • videos[] - Product videos
  • suppliers[] - Suppliers: suppliers_id and products_model (the supplier's article number). The list replaces all of the product's assignments; an entry without products_model clears the number.
  • storages[] - Inventory by warehouse v2.63.0+
  • languages{} - Name, description, SEO per language:
    • products_name, products_description, products_short_description
    • products_head_title_tag, products_head_desc_tag, products_head_keywords_tag
    • seo_name, products_url, products_checkout_description, products_image_title
  • options{} - Attributes without attribute groups (option name + values per language, SKU data per value)
  • option_groups[] - Combined attribute groups (e.g., linked “Color + Size”), SKU data per combination v2.58.0+
  • contenteditor{} - ContentEditor data per language

Attributes (Options)

There is no separate attribute endpoint. Attributes are associated with the product. Requests such as /Export/JSON/products_attributes or /Export/JSON/products_options therefore return the correct response: 501 Method not found.

Depending on the attribute type, the export provides two different structures:

FieldContainsSKU data (model/ean/quantity/price) belongs to …
options{}Attributes without an attribute groupSinglevalue
option_groups[] v2.58.0+Combined attribute groups, e.g., linked “Color + Size”Combination of values

There are intentionally two fields: for a characteristic group, there is one SKU per combination (“black / L” = one EAN), not per individual value. Merging them would create item numbers that do not exist. A product can have values in both fields.

"option_groups": [
  {
    "group": 1,
    "options": [
      { "id": 27, "languages": { "de": { "name": "Farbe" } } },
      { "id": 26, "languages": { "de": { "name": "Größe" } } }
    ],
    "combinations": [
      {
        "detail_id": 2344,
        "values": [
          { "options_id": 27, "options_values_id": 2483, "languages": { "de": { "name": "schwarz" } } },
          { "options_id": 26, "options_values_id": 304,  "languages": { "de": { "name": "L" } } }
        ],
        "model": "MS-01-0040-001-L",
        "ean": "4001",
        "quantity": "5",
        "price": "0.0000",
        "sort_id": 1
      }
    ]
  }
]
  • options[] Listed in display order;combinations[].values[] follows the same order—position 0 is always the first group member.
  • detail_id: 0 Empty SKU fields mean: nothing is maintained for this combination (not an error).
  • The import continues to expect the flat options[] format (v2.39.0)—convert on the client side for round trips.
Up to and including API 2.57.1, the export did not provide grouped attributes at all —no empty field, no warning. If you see “no attributes” in an older version, check in the backend whether the product’s options are in an attribute group. Fixed in API 2.58.0 (Shop 4.8.9).

Import (POST/PUT)

POST creates new products; PUT updates existing ones.

Since v2.4.2: Product descriptions are transmitted exclusively via the nested languages format (consistent with export). For INSERT (POST), missing languages are automatically populated with data from the master language (auto-fill).
New in v2.5.0: 19 extended fields for contract data (contract, contract_use_current_price), logistics (products_cost, products_min_qty, products_ean), and flags (service, sitemap, non_cart, non_price).
New in v2.17.0: Product special offers can be imported via specials[]. Supported URLs include customers_group_id, specials_new_products_price, or discount_percent, specials_begin,specials_end, specials_price_id,specials_price_name, specials_type, and deletion via action: delete.
New in v2.18.0: Shopping cart additions can be imported via upsells[]. Accepts an array of products_model strings or objects with {products_model, sort_order}. Replace semantics—existing entries are replaced. Self-references and unknown models are silently skipped.
New in v2.34.0: B-grade automation & additional fields: warranty_period (an empty string "" overrides the default 24m), products_xoport (markers, e.g., googleblacklist), images[] (additional images by filename, replaces the gallery, including alt text), storages[] (Per-warehouse inventory when multi-warehouse is active—fields, semantics, and pitfalls in the "Inventory & Reorder Levels per Warehouse" section) and downloads[] (product downloads, d_src = file at files/). POST /media uploads files using entity_type=download to files/.
New in v2.67.0: warranty_type can now be imported— physical, and digital are allowed; the spelling is corrected. The array_key_exists semantics apply: if the key is missing, the column remains unchanged; an empty string "" clears it. ⚠ An unknown value is rejected with a warning in warnings[] and is not automatically corrected—this field helps determine whether an item is classified as a physical product or digital content.
Customer Group/Retailer Prices (groups[]): For each product, different prices can be imported for each customer group (e.g., retailer groups). groups is an array of objects (not an ID list): each entry requires customers_group_id (> 0) and sets the net price for the group using customers_group_price —the gross price is automatically calculated based on the tax class. Optional tiered pricing: products_price1…products_price8 + products_price1_qty…products_price8_qty. Performs an upsert for each group; groups not included remain unchanged. The group “ 0 ” = “End customers” is set via products_price. You can determine the group IDs via the product export (field groups).
New in v2.57.0: Customer-specific prices (the “Customer-Specific Prices” tab in the Product Editor, table products_customers_prices) can be imported via customers_prices[] —including tiered pricing and the ability to selectively delete individual customer prices.

Customer-Specific Prices (customers_prices[]) v2.57.0

For each product, different prices can be imported for individual customers. The customer is identified via customers_id or customers_id_extern (WaWi customer number).

{
  "type": "products",
  "data": [{
    "products_model": "ABC-123",
    "customers_prices": [
      {"customers_id": 48, "customers_price": "9,99", "products_price1": 9.5, "products_price1_qty": 10},
      {"customers_id_extern": "20074", "customers_price": 8.88},
      {"customers_id": 55, "_action": "delete"}
    ]
  }]
}
  • Partial Upsert: Only the columns included in the import are written. Available: customers_price (alias price), products_price1…products_price12 + products_price1_qty…products_price12_qty (tiered pricing), products_qty_blocks, products_min_qty, products_max_qty.
  • Customer Addressing: via customers_id or customers_id_extern — the WaWi customer number must be unique; ambiguous numbers trigger a warning, and the entry is skipped.
  • _action: "delete" Removes the customer price for the respective customer.
  • customers_prices_replace: true = Full sync: First, all customer prices for the product are deleted; then the ones sent along are written. If customers_prices: [] is empty, all customer prices for the product are removed.
  • Insert defaults: If customers_price is missing, the current product price is used; quantity blocks and minimum quantity = 1.
  • customers_price ≤ 0 is ignored (warning)—removal occurs exclusively via ` _action: "delete"`.
Round-Trip: The product export has always included ` customers_prices `—exported data can be re-imported unchanged.

Inventory Levels & Reorder Points per Warehouse (storages[]) v2.45.0

When the multi-warehouse system is active, inventory, safety stock, reorder point, and storage location are maintained for each warehouse (table products_to_storages). The array has been available since v2.34.0; starting with v2.45.0, only the fields that are actually passed are written—a partial payload (e.g., only ` products_reorder_level`) no longer sets the remaining columns for this warehouse to ` 0`.

FieldTypeDescription
storages_id (Required)intID of the additional warehouse from the " storages " table (name in storages_info; maintained in the backend under Products → Warehouse Management). Alias: warehouse_id.
products_quantitydecimal(16,8)Stock in this warehouse.
products_safe_quantitydecimal(16,8)Safety stock for this warehouse.
products_reorder_leveldecimal(16,8)Reorder point for this warehouse.
products_storage_textvarchar(50)Storage location as free text, max. 50 characters. Is not automatically truncated.
options_idint, Default 0Characteristic/variant inventory. 0 = Inventory without characteristic reference.

Set only the reorder point for a warehouse —all other values for this warehouse remain unchanged:

{
  "type": "products",
  "data": [{
    "products_model": "ABC-123",
    "storages": [
      {"storages_id": 3, "products_reorder_level": 10}
    ]
  }]
}

Complete example with two warehouses, a characteristic inventory, and a separately set total inventory:

{
  "type": "products",
  "data": [{
    "products_model": "ABC-123",
    "products_quantity": 42,
    "storages": [
      {"storages_id": 1, "products_quantity": 30, "products_safe_quantity": 2, "products_reorder_level": 10, "products_storage_text": "Regal A-04"},
      {"storages_id": 3, "products_quantity": 12, "products_reorder_level": 5},
      {"storages_id": 3, "options_id": 2483, "products_quantity": 4}
    ]
  }]
}
  • Partial update (as of v2.45.0): Only fields present in the payload (INSERT … ON DUPLICATE KEY UPDATE) are written. Up to v2.44.0, the warehouse row was replaced using REPLACE —omitted columns were set to 0.
  • Key per inventory line: (storages_id, products_id, options_id). If the triple matches, the existing line is updated; otherwise, a new row is inserted.
  • No full mirroring: Inventory rows not in the array remain unchanged. There is no ` storages_replace ` and no ` _action: "delete" `—removing inventory rows is only possible in the backend.
  • No-op without user data: An entry containing only the key writes nothing and, in particular, does not create a row with zero values.
  • null is not “unchanged”: A field with the value null is considered to have been passed and writes 0. If a value is to remain unchanged, omit the field.
  • New warehouse row: Include inventory. If no row exists yet for the triplet, it is created solely from the fields passed. Therefore, when populating for the first time, send products_quantity and products_safe_quantity with — otherwise, the database server may reject the record depending on the configuration without the response reporting an error.
  • options_id: Default 0. Values greater than 0 maintain the inventory of a characteristic/variant combination in this warehouse; product and characteristic inventory are separate rows.
  • The decimal separator is the period: ` "50.5" ` is valid; ` "50,5" ` is interpreted as ` 50 `.
  • Prerequisite EXTENDED_STORAGE_SYSTEM = 'true': If the extended warehouse system is deactivated, storages is completely ignored; the response contains the warning STORAGE_SYSTEM_DISABLED.
  • Unknown or inactive warehouse: An ` storages_id` that does not exist or whose warehouse is inactive triggers the warning ` STORAGE_NOT_FOUND`; the entry is skipped, and the remaining entries continue to be processed.
  • Caution — missing storages_id is silently discarded: Entries without storages_id/warehouse_id or with a value of ≤ 0 are skipped without a warning. The import then reports success, even though no inventory levels were written.
The main warehouse is not an entry in storages: The “main warehouse” reorder point from the product editor is the product column products.products_reorder_level —a different field from storages[]. Starting with v2.63.0, it can be imported along with products_safe_quantity as a top-level field for the product; in older versions, it can only be imported via the backend or the CSV/XML column reorder_level. storages_id: 1 is already the first auxiliary warehouse —if you expect the reorder point for the main warehouse to appear there, you are entering it in the wrong warehouse.
products_quantity Is not calculated as a sum of the warehouse levels: The product’s total inventory is an independent column. If you set warehouse inventories via ` storages `, you must separately include ` products_quantity ` in the same request—otherwise, the old total inventory will remain.
Round-trip: Starting with v2.63.0, theJSON export includes ` storages[] `—with the same set of fields—so exported inventory data can be reimported unchanged. If the extended inventory system is disabled, the key is missing entirely (not as an empty array). All inventory lines for the product are output, including those for inactive warehouses— STORAGE_NOT_FOUND then confirms their re-import. Up to v2.62.0, the export did not include inventory levels; in those versions, you’ll need to use the CSV Porter (columns storage_<ID>_*) or the backend.

Example Request

curl -X POST "https://shop.de/xpanel/xoport/import/json/products" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "products",
    "data": [{
      "products_model": "TEST-001",
      "products_price": 99.99,
      "products_quantity": 50,
      "manufacturers_id": 1,
      "contract": 1,
      "contract_use_current_price": 1,
      "products_ean": "4012345678901",
      "categories": [5, 12],
      "groups": [
        {"customers_group_id": 2, "customers_group_price": 1975.00},
        {"customers_group_id": 3, "customers_group_price": 1975.00}
      ],
      "suppliers": [{"suppliers_id": 3, "products_model": "LF-4711"}],
      "specials": [{
        "customers_group_id": 0,
        "discount_percent": 10,
        "specials_price_name": {"de": "Muttertag", "en": "Mother Day"},
        "status": 1,
        "specials_begin": "2026-05-05 00:00:00",
        "specials_end": "2026-05-11 23:59:59"
      }],
      "languages": {
        "de": {
          "products_name": "Testprodukt",
          "products_description": "Beschreibung...",
          "products_head_title_tag": "SEO Titel",
          "products_head_desc_tag": "Meta Description",
          "seo_name": "testprodukt"
        }
      }
    }]
  }'

Delete (DELETE)

Query Parameters

ParametersTypeDescription
products_idintProduct ID to delete
products_modelstringItem number to delete

Example

curl -X DELETE "https://shop.de/xpanel/xoport/delete/json/products?products_id=123" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"