Produits Endpoint
Produits
Opérations CRUD complètes pour les données produit.
Aperçu
| Opération | Méthode | Point de terminaison | Portée |
|---|---|---|---|
| Tout exporter | GET | /xpanel/xoport/export/json/products | products:read |
| Produit individuel | GET | /xpanel/xoport/export/json/product/{ID} | products:read |
| Plage d'identifiants | GET | /xpanel/xoport/export/json/products/{START}:{END} | products:read |
| Importation | POST | /xpanel/xoport/import/json/products | products:write |
| Mise à jour | PUT | /xpanel/xoport/import/json/products | products:write |
| Supprimer | DELETE | /xpanel/xoport/delete/json/products/{ID} | products:delete |
Exportation (GET)
/product/{ID} » (au singulier) pour un seul produit.Modèles d'URL
| URL | Description |
|---|---|
/export/json/product/123 | Produit unique avec l'ID 123 ; en l'absence de résultat, code HTTP 404 NOT_FOUND v2.122.10 |
/export/json/products | Tous les produits (pagination) |
/export/json/products/100:200 | Produits dont l'ID est compris entre 100 et 200 |
/export/json/products/100: | Produits à partir de l'ID 100 (plage ouverte, pour l'instant uniquement le produit 100) v2.122.10 |
/export/json/product/123%20 | L'espace vide à la fin de l'ID n'est pas pris en compte : produit 123 v2.122.12 |
Segment de chemin non valide : /export/json/product/123x, /product/123.0, /product/1e3, /product//123, /products/productlist | HTTP 400 INVALID_RANGE, le message mentionne le segment. Jusqu’à présent, la liste des produits ou un article réinterprété (1e3 = 1000) v2.122.12 |
Depuis la v2.122.12, le segment correspondant à /product ou /products est un nombre entier ou une plage délimitée par des nombres entiers ; envoyez l’ID sans décimales et sans signe. Toutes les règles : API d’exportation, section « Segment de chemin ».
Paramètres de requête
| Paramètres | Type | Description |
|---|---|---|
limit | int | Nombre maximal (par défaut : 50, max. : 250) |
offset | int | Position de départ pour la pagination |
bonus_free_features | 0/1 | Avec « 1 », chaque produit comporte le bloc « bonus_free_features » (données bonus et gratuites) v2.122.10 |
bundles | 0/1 | Avec 1, chaque produit comporte la clé bundle (voir les offres groupées) v2.122.11 |
marketplaces | 0/1 | Avec 1, chaque produit comporte marketplaces[], les commutateurs et l'état par canal (voir les places de marché par article) v2.122.11 |
fields | Liste séparée par des virgules | Uniquement ces clés par produit (voir sélection des champs) v2.122.11 |
seo_name | Liste séparée par des virgules | Articles sous une adresse de boutique : slug effectif, exact, toutes langues v2.122.11 |
marketplace, marketplace_status, marketplace_upload_status, marketplace_sync_state | Texte, liste séparée par des virgules | Filtres de place de marché (voir filtres de place de marché) v2.122.11 |
Depuis la v2.122.10, les valeurs non valides et les valeurs de liste telles que ?limit[]=50 génèrent une erreur HTTP 400, tandis que INVALID_FILTER_VALUE avec details[] fonctionne.
Depuis la v2.122.12, les espaces en bordure ne sont plus pris en compte : ?bundles=%201 (ainsi que marketplaces et bonus_free_features) renvoie le bloc ; jusqu’à la v2.122.11, celui-ci était tacitement absent. Une valeur vide signifie « non défini ».
Depuis la v2.122.11, une valeur composée uniquement de virgules sur products_model, products_ean ou seo_name renvoie une erreur HTTP 400 INVALID_FILTER_VALUE (« holds no value »), tout comme un filtre de date composé uniquement de signes de ponctuation, tel que ?last_modified=.. Auparavant, on obtenait la liste non filtrée ou uniquement les modifications apportées depuis ce moment-là. Les autres filtres (status, categories_id, ean_prefix, last_modified …) se trouvent sous « Export API », tandis que la recherche par adresse de boutique se trouve sous « SEO History ».
Exemples de requêtes
# 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"Champs de réponse
Chaque produit contient :
products_id,products_model,products_eanproducts_price,products_weight,products_quantity,products_costmanufacturers_id,products_statuscontract,contract_use_current_price,contract_optionv2.5.0products_inventory_management,products_min_qty,products_max_qty,products_qty_blocks,products_base_pricev2.5.0service,sitemap,non_cart,non_price,products_free_shippingv2.5.0shipping_profile,products_unit_id,base_unit_id,products_sort_orderv2.5.0images[]- Images des produits avec tri. Les articles sans image principale ne génèrent plus d'entrée vide avecimages_id0 v2.122.10categories[]- Identifiants de catégoriesgroups[]- Tarifs par groupes de clientscustomers_prices[]- Prix personnalisés par clientspecials[]- Prix spéciauxextra_fields[]- Champs supplémentairesbadges[]- Badges de produitsxsells[]- Produits de vente croisée, complets et conformes à la norme «sort_order» v2.122.9upsells[]- Compléments au panier (« À associer ») v2.18.0+tabs[]- Onglets de produitsbonus_free_features- Uniquement avec?bonus_free_features=1:status_bonus,bonus_points(prix en points par unité),max_bonus,status_free,bonus_value(valeur marchande par article gratuit),max_free,auto_free; en lecture seulebonus_date_added,bonus_last_modified,date_status_changed. Sans donnéesnullv2.122.10bundle- uniquement avec?bundles=1: paramètres et composants d'un bundle, sans bundlenullv2.122.11marketplaces[]- uniquement avec?marketplaces=1: une entrée par canal avec interrupteur et état v2.122.11videos[]- Vidéos des produitssuppliers[]- Fournisseurs :suppliers_idetproducts_model(référence du fournisseur). La liste remplace toutes les affectations de l'article ; une entrée sansproducts_modelefface la référence.storages[]- Stock par entrepôt v2.63.0+languages{}- Nom, description, référencement naturel par langue :products_name,products_description,products_short_descriptionproducts_head_title_tag,products_head_desc_tag,products_head_keywords_tagseo_name,products_url,products_checkout_description,products_image_title
languages.<code>.unit_single,unit_multi,unit_base- Unité par langue, renseignée ; si aucune unité, laisser vide au lieu denullv2.122.9options{}- caractéristiques sans groupe de caractéristiques (nom de l'option + valeurs par langue, données SKU par valeur)option_groups[]- groupes de caractéristiques combinés (par ex. « couleur + taille » couplées), données SKU par combinaison v2.58.0+contenteditor{}- Données ContentEditor par langue
Autres champs du produit
Outre les champs ci-dessus, l’exportation fournit les autres colonnes de l’article ; depuis la v2.105.0, la plupart sont également modifiables. Pour ces champs, le nom peut être trompeur ou leur portée va plus loin que prévu :
| Champ | Signification |
|---|---|
products_status | 0 inactif, 1 actif et visible, 2 actif, mais sans chemin de catégorie actif de bout en bout : invisible sur l'interface publique, affiché comme actif dans l'interface d'administration. Envoyez uniquement 0 ou 1; la valeur 2 est définie automatiquement par la boutique (depuis la v2.122.10 également lors de l’importation, avertissement NO_ACTIVE_CATEGORY_PATH). Si une connexion active les catégories via l’API, la boutique ne synchronise pas le statut des articles ; une requête produitPUT avec "products_status": 1 permet de rattraper ce retard. |
amazon_allowed | ⚠️ Blocage d’Amazon Pay, malgré son nom : 1 = bloqué. Si un tel article se trouve dans le panier, la boutique n’affiche pas le bouton « Express » d’Amazon Pay (panier, connexion, panier latéral). Dans la sélection des modes de paiement à la caisse, Amazon Pay reste sélectionnable. Pas de bouton « Place de marché ». |
ebay_allowed | « Article pour eBay » dans le module eBay classique : seul 1 propose l’article à cet endroit. ⚠️ 0 met fin à une annonce en cours lors de la prochaine synchronisation. L’intégration eBay via les places de marché n’évalue pas ce champ ; les options de marketplaces[] s’appliquent ici. |
price_declaration_id | « Remarque concernant les remises » : ID du texte qui précède le prix barré (par défaut 1, « Notre ancien prix »). Il n’apparaît que si un prix spécial ou une remise s’applique. L’importation ne vérifie pas si l’ID existe ; un ID inconnu n’affiche aucun texte. |
products_xoport | Mots de passe pour les flux, les places de marché et CSV-Porter, séparés par des points-virgules, 64 caractères maximum. Dans un flux de produits, on exclut les articles dont l’adresse du flux contient « &products_xoport=<Kennwort>&blacklist=true ». ⚠️ Les flux et les places de marché sont comparés par mots partiels : meta correspond également à ohnemeta. Sur les places de marché, une exclusion met fin aux offres existantes lors de la prochaine synchronisation. |
lia_exclude | 1 = à ne pas inclure dans le flux « stock local », sinon cela n’a aucun effet. Si ce champ est absent de l’export d’une boutique, ne pas l’envoyer : sinon, l’importation s’interrompt avec un code HTTP 500, et les enregistrements précédents sont déjà enregistrés. |
importflag | ⚠️ Marque d’origine des importations de gestion des stocks. Un importateur considère les articles portant sa marque comme les siens et les supprime ou les désactive s’ils manquent lors de son prochain cycle. Ne modifier que de manière délibérée. |
import_mask | Ancien champ, en lecture seule (READONLY_FIELD). Ni le backend, ni CSV-Porter, ni cette API ne l’écrivent ; ne pas l’analyser. |
Sélection des champs (?fields=) v2.122.11
Pour /products, /product/{ID}, /products/{ID} et les sections, ?fields= indique les clés de chaque produit sous forme de liste séparée par des virgules. La boutique ne charge que ces données : lors d’un test, une page comportant 250 articles pesait 4,3 Mo sans et 52 Ko avec fields=products_model,products_price,products_quantity,products_status.
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
"https://shop.de/xpanel/xoport/export/json/products?status=1&limit=250&fields=products_model,products_price,products_quantity"
# → "data": [{"products_id": 28, "products_model": 8624000, "products_price": 12.5, "products_quantity": 3}, …]- Les clés valides sont celles du niveau supérieur de l'enregistrement de données du produit : chaque colonne du produit (y compris les colonnes propres à la boutique),
manufacturers_idet les clés de bloc figurant dans la liste ci-dessous.products_idest toujours inclus, l'ordre correspond à celui de l'enregistrement. - Noms issus de
a–z,0–9et_, 64 caractères maximum chacun. 100 éléments de liste au maximum ; les éléments vides et les doublons sont comptabilisés ; un doublon n’est pris en compte qu’une seule fois. Unfields=vide ou ne contenant que des virgules (fields=,) est considéré comme non défini. - Absence totale au lieu de valeur vide : une clé non mentionnée est absente de la réponse ; on n’y trouve jamais
[]ounull. De même, une clé mentionnée mais que l’article ne possède pas est absente, par exemplespecialsans prix spécial. Une sélection peut donc être renvoyée viaPUT, y compris avec?_strict=true, et ne modifie que ce qu’elle contient. - La colonne
shipping_profilene fournit que l’ID du profil ; vous indiquez la résolution avecshipping_profile_effective,shipping_profile_name,shipping_profile_stepoushipping_profile_text. bundle,bonus_free_featuresetmarketplaces: avec?fields=, le nom demande le bloc, sans commutateur. Un commutateur sans ce nom ne fonctionne alors pas (avertissementSWITCH_IGNORED).- Sans
?fields=, la réponse reste inchangée. L'exportation XML ne reconnaît pas ce paramètre.
Clé de bloc : un bloc n’est chargé que si l’une de ses clés est mentionnée. Le contenu varie en fonction de la clé, et lorsque la clé est manquante :
groups– Prix par groupe de clients, groupe 0 issu du produitmanufacturers– Noms des fabricants versmanufacturers_idlanguages– Textes par langue, y compris les unitéscustomers_prices– Prix clients ; absent s’il n’y a pas de prix clientrestricted_customer_groups– groupes de clients bloqués ; s’il manque, aucun blocageimport_blocks– Blocages d’importation ; s’il n’y en a pas, pas de blocagecategories– Affectations de catégories ; manquant sans affectationimages– Images principales et supplémentaires ;[]en l’absence d’imageextra_fields– Champs supplémentaires ; sans champ, l’[]n’est possible qu’avec un système de gestion des stocks avancé, sinon il est manquantbadges– Badges ; manquant s’il n’y a pas de badgexsells– Vente croisée ; absent sans entréeupsells– Vente incitative ; manquant s’il n’y a pas d’entréetabs– Onglets dans toutes les langues ; absents sans ongletsspecial– Prix spécial ; absent sans prix spécialoptions– Caractéristiques sans groupe ; manquant sans caractéristiqueoption_groups– Groupes de caractéristiques combinés ; manquant sans groupevideos– Vidéos ; manquant sans vidéosuppliers– Fournisseurs ; manquant sans attributionshipping_profile_effective,shipping_profile_name,shipping_profile_step,shipping_profile_text– Profil « feu tricolore » (un bloc commun pour les quatre) : le profil en vigueur, son nom, le niveau correspondant au stock actuel et le texte relatif au délai de livraison ; les quatre éléments sont absents si la boutique ne dispose pas de profils « feu tricolore »quicksearch– Nombre d’entrées de recherche rapide par langue ({}= non indexé)options_rules– Règles d’options du configurateur ; absent s’il n’y a pas de règlecontenteditor– Données ContentEditor de la description ; uniquement avec la licence xoContentEditor et les donnéesstorages– Lignes de stock ; uniquement avec le système de gestion des stocks étendubundle– Lot,nullsans lot ; sans?fields=uniquement avec?bundles=1bonus_free_features– Données relatives aux bonus et aux articles gratuits, «null» sans entrée ; sans «?fields=», uniquement avec?bonus_free_features=1marketplaces– une inscription par chaîne ; sans?fields=, uniquement avec?marketplaces=1
| demande | Réponse |
|---|---|
?fields=produts_model,products_price | HTTP 200, products_price fonctionne ; avertissement « UNKNOWN_FIELD » avec suggestion: "products_model" |
le même avec &_strict=true | HTTP 400 UNKNOWN_FIELD, aucune donnée |
?fields=Products_Model, ?fields=languages.products_name, ?fields=-extra_fields, ?fields=*, ?fields[]=… ou plus de 100 éléments dans la liste | HTTP 400 INVALID_FILTER_VALUE |
?bundles=1&fields=products_model | HTTP 200 sans bundle, avertissement SWITCH_IGNORED |
?fields= recevaient l’ensemble des données ainsi que l’avertissement UNKNOWN_FILTER. Désormais, seules les clés mentionnées sont fournies, et une liste invalide génère une erreur HTTP 400.Caractéristiques (options)
options{} et option_groups[] (structures ci-dessous). Depuis la v2.96.0, le catalogue des caractéristiques, indépendamment d’un produit, peut être consulté via GET /Export/JSON/products_options (liste sans valeurs), GET /Export/JSON/products_option/{ID} (une caractéristique avec ses valeurs) et GET /Export/JSON/products_options_values (portée products:read) ; POST/PUT /Import/JSON/products_options permet de créer et de modifier des caractéristiques (portée products:write). Un appel à /Export/JSON/products_attributes renvoie une erreur HTTP 501 NOT_IMPLEMENTED; ce point de terminaison n'existe pas.Selon le type de caractéristique, l'exportation fournit deux structures différentes :
| Champ | Contient | Les données SKU (model/ean/quantity/price) appartiennent à… |
|---|---|---|
options{} | Caractéristiques sans groupe de caractéristiques | Valeur unique |
option_groups[] v2.58.0+ | groupes de caractéristiques combinés, par exemple « couleur + taille » couplées | Combinaison des valeurs |
Il y a délibérément deux champs : pour un groupe de caractéristiques, il existe une référence par combinaison (« noir / L » = un code EAN), et non par valeur individuelle. Une fusion entraînerait la création de références qui n’existent pas. Un produit peut comporter ces deux champs.
"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[]s’affiche dans l’ordre d’affichage;combinations[].values[]suit le même ordre — la position 0 correspond toujours au premier élément du groupe.detail_id: 0Des champs SKU vides signifient : pour cette combinaison, aucune donnée n’est renseignée (ce n’est pas une erreur).- L’importation attend toujours le format plat «
options[]» (v2.39.0) — convertir côté client pour les allers-retours.
Importation (POST/PUT)
POST crée de nouveaux produits, PUT met à jour ceux qui existent déjà.
languages(conformément à l’exportation). Lors d’ une opération INSERT (POST), les langues manquantes sont automatiquement renseignées avec les données de la langue principale (remplissage automatique).contract, contract_use_current_price), la logistique (products_cost, products_min_qty, products_ean) et les indicateurs (service, sitemap, non_cart, non_price).specials[]. Les formats pris en charge sont customers_group_id, specials_new_products_price ou discount_percent, specials_begin/specials_end, specials_price_id/specials_price_name, specials_type et la suppression via action: delete.upsells[]. Accepte un tableau de chaînes products_model ou d’objets avec {products_model, sort_order}. Sémantique de remplacement — les entrées existantes sont remplacées. Les références à soi-même et les modèles inconnus sont ignorés tacitement.warranty_period (une chaîne vide "" remplace la valeur par défaut 24m), products_xoport (marqueurs, par exemple googleblacklist), images[] (images supplémentaires par nom de fichier, remplace la galerie, y compris les textes alternatifs), storages[] (stock « Pro » lorsque la gestion multi-entrepôts est activée — champs, sémantique et pièges dans la section « Stocks et seuils de réapprovisionnement par entrepôt ») et downloads[] (téléchargements de produits, d_src = fichier disponible sur files/). POST /media télécharge les fichiers via entity_type=download vers files/.warranty_type est désormais importable – les valeurs autorisées sont physical et digital; l’orthographe est corrigée. La sémantique de array_key_exists s’applique : si la clé est absente, la colonne reste inchangée ; une chaîne vide "" la vide. ⚠ Une valeur inconnue est rejetée avec un avertissement dans warnings[] et n’est pas corrigée – ce champ permet de déterminer si un article est considéré comme une marchandise ou comme un contenu numérique.groups[]) : pour chaque produit, il est possible d’importer des prix différents selon le groupe de clients (par exemple, des groupes de revendeurs). groups est un tableau d’objets (pas une liste d’identifiants) : chaque entrée nécessite un « customers_group_id » (> 0) et définit, via « customers_group_price », le prix net du groupe — le prix brut est calculé automatiquement à partir de la classe fiscale. Prix échelonnés facultatifs : products_price1…products_price8 + products_price1_qty…products_price8_qty. Opération « upsert » par groupe ; les groupes non concernés restent inchangés. Le groupe 0 = « Clients finaux » est défini via products_price. Vous pouvez déterminer les ID de groupe via l’exportation des produits (champ groups).products_customers_prices) peuvent être importés via customers_prices[] — y compris les prix dégressifs et la suppression ciblée de certains prix clients.restricted_customer_groups ne reprend que les groupes de clients existants, sous forme de nombre entier. Si la liste ne contient aucun groupe existant ou si elle n’est pas une liste, les blocages enregistrés restent inchangés (avertissement CUSTOMER_GROUP_RESTRICTION_KEPT) ; les entrées inconnues à côté d’entrées connues sont signalées par CUSTOMER_GROUP. [] continue de lever tous les blocages.- Une liste
import_blockssans entrée exploitable ne modifie plus les blocages. Tous les types de blocage de la boutique peuvent être définis. Nouveautés :import_blocks_mode(replace,mergeouappend,remove) etrecords[].import_blocks. Les boutiques plus anciennes ignorent ce mode ; il faut donc l’envoyer uniquement avec?_strict=true. - Un
categories_modenon valide, une catégorie inconnue, uncategoriesou unimagessans liste, ainsi qu’unimages[]sans entrée valable entraînent le rejet de l’enregistrement avant son écriture (INVALID_CATEGORIES_MODE,INVALID_CATEGORIES,INVALID_IMAGES). Une requête POST rejetée ne crée rien ; chaque rejet est accompagné d’un code figurant dansrecords[]. options_replace,options_strict_names,apply_value_defaults,videos_replaceetcustomers_prices_replacene s’activent qu’avectrue,1,"1"ou"true".- Votre propre exportation peut être renvoyée via
?_strict=true: seuls les champs en lecture seule tels quequicksearchetoption_groupssignalentREADONLY_FIELD.
products_statusn'accepte que 0 ou 1 (y compris"0"/"1",true/false). La boutique définit elle-même le statut 2 lorsque l'article ne dispose pas d'un chemin de catégorie actif sur toute sa longueur (avertissementNO_ACTIVE_CATEGORY_PATH). Une valeur 2 envoyée signaleREADONLY_VALUE, une valeur non valideINVALID_FIELD_VALUE; aucune des deux n'est enregistrée, contrairement au reste de l'enregistrement. Nouveau :records[].products_status.products_xoportAccepte au maximum 64 caractères ; une valeur plus longue entraîne le rejet de l'enregistrement.- Données bonus et gratuites via
bonus_free_features: clé manquante = inchangé,null= supprimer, objet = uniquement les champs envoyés. Les valeurs non valides rejettent l’enregistrement (INVALID_BONUS_FREE_FEATURES), le blocage d’importationbonusest respecté. Réponserecords[].bonus_free_features:written,unchanged,removedoublocked. - Une entrée d’image « pas d’image principale » provenant d’exportations antérieures (
images_id0,images_filevide ) est ignorée ; l’image principale enregistrée est conservée. Poursuivez la suppression avec"products_image": "". - Blocage d’importation
costpour le prix d’achat. Vérifier au préalableapi_version: les boutiques plus anciennes n’enregistrent pas ce type, et jusqu’à la version 2.122.8, une liste ne supprime que les blocages existants decost.
bundleest traité : jusqu’à présent, unbundleenvoyé était ignoré et remplacé parUNKNOWN_FIELD. Désormais, cela modifie le bundle, et unbundlenon valide entraîne l’échec de l’ensemble de l’enregistrement (INVALID_BUNDLE). Voir Bundles.marketplaces[]Enregistre les commutateurs de la place de marché par canal ; jusqu’à présent, la clé étaitUNKNOWN_FIELD. Une exportation renvoyée avec?marketplaces=1enregistre également les commutateurs. Voir Places de marché par article.- Une sélection issue de
?fields=peut être renvoyée et ne modifie que les clés mentionnées.
Prix personnalisés par client (customers_prices[]) v2.57.0
Pour chaque produit, il est possible d’importer des prix différents pour des clients individuels. Le client est identifié via customers_id ou customers_id_extern (numéro de client WaWi).
{
"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"}
]
}]
}- Upsert partiel : seules les colonnes transmises sont écrites. Disponibles :
customers_price(aliasprice),products_price1…products_price12+products_price1_qty…products_price12_qty(prix échelonnés),products_qty_blocks,products_min_qty,products_max_qty. - Identification du client : via
customers_idoucustomers_id_extern— le numéro client du système de gestion commerciale doit être unique ; les numéros ambigus génèrent un avertissement et l'entrée est ignorée. _action: "delete"Supprime le prix client correspondant à ce client.customers_prices_replace: true= Synchronisation complète : tous les prix client du produit sont d’abord supprimés, puis ceux fournis sont enregistrés. Si le champ «customers_prices: []» est vide, tous les prix client du produit sont supprimés.- Valeurs par défaut d’insertion : si
customers_priceest manquant, le prix actuel du produit est repris ; blocs de quantité et quantité minimale = 1. customers_price≤ 0 est ignoré (avertissement) — la suppression s’effectue exclusivement via_action: "delete".
customers_prices — les données exportées peuvent être réimportées sans modification.Stocks et seuils de réapprovisionnement par entrepôt (storages[]) v2.45.0
Lorsque le système multi-entrepôts est activé, le stock, le stock de sécurité, le point de commande et l'emplacement sont gérés pour chaque entrepôt (table products_to_storages). Ce tableau existe depuis la v2.34.0 ; depuis la v2.45.0, seuls les champs effectivement transmis sont enregistrés — une charge utile partielle (par exemple, uniquement products_reorder_level) ne définit plus les colonnes restantes de ce magasin sur 0.
| Champ | Type | Description |
|---|---|---|
storages_id (obligatoire) | int | ID de l'entrepôt supplémentaire issu de la table storages (désignation dans storages_info, gestion dans le backend sous Produits → Gestion des stocks). Alias : warehouse_id. |
products_quantity | decimal(16,8) | Stock dans cet entrepôt. |
products_safe_quantity | decimal(16,8) | Stock de sécurité de cet entrepôt. |
products_reorder_level | decimal(16,8) | Seuil de réapprovisionnement de cet entrepôt. |
products_storage_text | varchar(50) | Emplacement sous forme de texte libre, 50 caractères max. N'est pas automatiquement tronqué. |
options_id | int, par défaut 0 | Stock par caractéristique/variante. 0 = stock sans référence à une caractéristique. |
Définir uniquement le seuil de réapprovisionnement d’un entrepôt — toutes les autres valeurs de cet entrepôt restent inchangées :
{
"type": "products",
"data": [{
"products_model": "ABC-123",
"storages": [
{"storages_id": 3, "products_reorder_level": 10}
]
}]
}Exemple complet avec deux entrepôts, un stock par caractéristique et un stock total défini séparément :
{
"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}
]
}]
}- Mise à jour partielle (à partir de la v2.45.0) : seules les zones présentes dans la charge utile (
INSERT … ON DUPLICATE KEY UPDATE) sont écrites. Jusqu’à la v2.44.0, la ligne de stock était remplacée parREPLACE— les colonnes omises étaient alors remplacées par0. - Clé par ligne de stock :
(storages_id, products_id, options_id). Si le triplet est identique, mise à jour de la ligne existante ; sinon, insertion. - Pas de synchronisation complète : les lignes de stock qui ne figurent pas dans le tableau restent inchangées. Il n’y a ni
storages_replaceni_action: "delete"— la suppression de lignes de stock n’est possible que dans le backend. - Opération vide sans données utiles : une entrée ne contenant que la clé n’écrit rien et ne crée notamment pas de ligne avec des valeurs nulles.
nulln’est pas « inchangé » : un champ dont la valeur estnullest considéré comme transmis et écrit0. Si une valeur doit rester inchangée, omettez le champ.- Nouvelle ligne de stock : envoyer le stock. Si aucune ligne n’existe encore pour le triplet, celle-ci est créée uniquement à partir des champs transmis. Lors du premier remplissage, envoyez donc
products_quantityetproducts_safe_quantityavec — sinon, le serveur de base de données peut rejeter l’enregistrement en fonction de la configuration, sans que la réponse ne signale d’erreur. options_id: Valeur par défaut0. Les valeurs > 0 gèrent le stock d’une combinaison caractéristique/variante dans ce magasin ; le stock produit et le stock caractéristique font l’objet de lignes distinctes.- Le séparateur décimal est le point : «
"50.5"» est valide, «"50,5"» est lu comme «50». - Condition préalable
EXTENDED_STORAGE_SYSTEM = 'true': si le système de stock étendu est désactivé,storagesest entièrement ignoré ; la réponse contient l'avertissementSTORAGE_SYSTEM_DISABLED. - Entité inconnue ou inactive : uneentité
storages_idqui n’existe pas ou dont l’entité n’est pas active génère l’avertissementSTORAGE_NOT_FOUND; l’entrée est ignorée, les entrées restantes continuent à être traitées. - Attention — les
storages_idmanquantes sont ignorées en silence : les entrées sansstorages_id/warehouse_idou dont la valeur est≤ 0sont ignorées sans avertissement. L’importation signale alors qu’elle s’est déroulée avec succès, bien qu’aucun stock n’ait été enregistré.
storages: le seuil de réapprovisionnement « Entrepôt principal » de l'éditeur de produits correspond à la colonne products.products_reorder_level — un champ différent de storages[]. À partir de la version 2.63.0, il peut être importé avec products_safe_quantity en tant que champ de niveau supérieur du produit ; dans les versions antérieures, uniquement via le backend ou la colonne CSV/XML reorder_level. storages_id: 1 correspond déjà au premier entrepôt supplémentaire — si vous vous attendez à y trouver le seuil de réapprovisionnement de l’entrepôt principal, vous saisissez les données dans le mauvais entrepôt.products_quantity n’est pas calculé à partir des stocks des entrepôts : le stock total du produit est une colonne indépendante. Si vous définissez les stocks via storages, vous devez envoyer séparément products_quantity dans la même requête — sinon, l’ancien stock total reste inchangé.storages[] — avec le même ensemble de champs ; les données de stock exportées peuvent donc être réimportées sans modification. Si le système de stock étendu est désactivé, la clé est totalement absente (et non sous forme de tableau vide). Toutes les lignes de stock du produit sont générées, y compris celles correspondant à des entrepôts inactifs — leur réimportation est alors confirmée par STORAGE_NOT_FOUND. Jusqu’à la v2.62.0, l’export ne fournissait pas les stocks ; dans ce cas, il fallait passer par le CSV-Porter (colonnes storage_<ID>_*) ou le backend.Exemple de requête
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"
}
}
}]
}'Ensembles v2.122.11
Composition et paramètres d’un bundle associé à un produit, portées products:read et products:write. Jusqu’à la version v2.122.10, l’API REST ne reconnaissait pas ce champ : un bundle envoyé était ignoré et traité comme UNKNOWN_FIELD, tandis que ?bundles=1 renvoyait l’erreur UNKNOWN_FILTER.
Exportation
Uniquement sur demande : GET /products?bundles=1, ainsi que /product/{ID} et les sections (uniquement 0 ou 1, sinon HTTP 400 INVALID_FILTER_VALUE). Avec ce paramètre, chaque produit de la page porte la clé : null sans bundle, sinon les paramètres et items[] exactement sous la forme lue par l’importation. Avec ?fields=, le nom bundle demande le bloc ; ?bundles=1 sans le nom ne fonctionne alors pas (avertissement SWITCH_IGNORED).
Importation
{ "type": "products", "data": [ { "products_model": "SET-100",
"bundle": { "bundle_status": 1, "bundle_cart_listing": 1,
"items": [ { "products_model": "SHIRT-1", "qty": 1, "options_name": "Farbe > Größe", "options_value": "Rot > XL" },
{ "uprid": "4711{3}12{5}40", "qty": 2 } ] } } ] }bundle | Effet |
|---|---|
| Clé manquante | Le bundle reste inchangé |
null ou {"delete": true} | Supprimer le bundle, les paramètres et la composition |
Objet avec items[] | paramètres (uniquement ceux envoyés) et composants sous forme de miroir complet : les composants non mentionnés sont omis |
Objet sans items ou avec un items | uniquement les paramètres |
Tout ou rien par enregistrement : la structure, les paramètres et chaque composant sont vérifiés avant l'écriture. Un composant inconnu, une auto-référence, une quantité invalide, une sélection d'attributs ou un paramètre entraîne l'échec de l'enregistrement avec INVALID_BUNDLE; details[] indique les raisons, et rien n'est écrit, pas même le prix de cet enregistrement.
Paramètres
| Champ | Signification |
|---|---|
bundle_status | Bundle actif ; un nouveau bundle commence à 1 |
bundle_category_listing, bundle_product_listing, bundle_cart_listing | Afficher les composants dans la catégorie, sur la page du produit et dans le panier |
bundle_image_listing, bundle_options_listing | Afficher les images ou les caractéristiques des composants |
bundle_save_in_pieces, bundle_main_as_product | Afficher les composants individuellement dans la commande, l'article principal en tant que produit distinct |
bundle_spread_status, bundle_auto_qty | Transmettre le statut aux composants, créer un stock à partir des composants |
bundle_copy_to_slaves | Au niveau de l'article maître : les paramètres et la composition s'appliquent aux variantes actives |
bundle_auto_price, bundle_auto_price_percent | Non disponible dans Shop 4.9 : avertissement « BUNDLE_SETTING_UNAVAILABLE », rien n’est indiqué |
Valeurs : 1/0, true/false, ja/nein, yes/no, on/off; null = non spécifié. Les autres boutons d’un nouveau bundle sont désactivés.
Composants (items[])
| Champ | Signification |
|---|---|
products_id ou products_model | composant par ID (priorité) ou référence |
uprid | Sélection simultanée du produit et de la sélection d'attributs, par exemple 4711{3}12{5}40 ou simplement {3}12{5}40; lorsqu’il est associé à products_id ou products_model, les identifiants doivent correspondre ; lorsqu’il est associé à des champs de caractéristiques, la sélection obtenue doit être identique |
qty | Quantité supérieure à 0, champ vide = 1 |
sort | Ordre, vide = position dans items[] |
options_name et options_value | caractéristiques par nom dans la langue par défaut, séparées par « > » (Farbe > Größe) ; plusieurs valeurs d’une case à cocher séparées par une virgule |
options_id et options_value_id | caractéristiques par ID, séparées par « _ » ; les ID ont la priorité |
Les entrées identiques (produit et sélection) sont regroupées en une seule ligne avec une quantité cumulée. Un composant ne peut pas être le lot lui-même.
Réponse
records[].bundle:written,settings,removedounone.stats.bundlesavecprocessed,written,settingsetremoved, si le lot portait la référencebundle.INVALID_BUNDLEavecdetails[], par exempleitem 2: NOCOMPONENT SHIRT-9,item 1: QTY 0ouSETTING bundle_cart_listing.
- L’exportation convertit en chiffres les références d’articles purement numériques et les adresses
upridsans sélection ; l’importation accepte les deux. Si vous avez besoin de texte, utilisez?numeric_check=false. - Boutiques provenant d’une version antérieure à la 4.9.155 : après la mise à jour, ouvrez une fois le backend avant que l’intégration ne crée des lots. Sinon, un lot comportant deux combinaisons de caractéristiques du même produit s’interrompra.
Marchés par article v2.122.11
L’onglet « Places de marché » du masque de produit active un produit par canal ; la liste des produits multicanal affiche à cet effet le statut de téléchargement, le statut de vérification et l’état « actuel » ou « en attente ». Depuis la v2.122.11, ces informations peuvent être lues, écrites et filtrées via l’API. Jusqu’à la v2.122.10, l’API ne prenait pas en charge ce bloc.
Lecture
Uniquement sur demande : GET /products?marketplaces=1, ainsi que /product/{ID}, /products/{ID} et les sections (uniquement 0 ou 1, sinon HTTP 400 INVALID_FILTER_VALUE). Chaque article comporte une entrée pour chaque canal de la boutique, classées par ordre croissant selon marketplace_id, même sans validation (dans ce cas, status 0). Avec ?fields=, le nom marketplaces demande le bloc ; ?marketplaces=1 sans le nom ne fonctionne alors pas (avertissement SWITCH_IGNORED).
GET /xpanel/xoport/Export/JSON/product/4711?marketplaces=1
{"products_id": 4711, "products_model": "ART-1", "…": "…",
"marketplaces": [
{"marketplace_id": 2, "code": "idealo", "name": "Idealo", "licensed": true, "adapter": true,
"status": 1, "upload_status": "done", "verify_status": null, "sync_state": "pending",
"last_updated": "2026-10-13 23:00:00", "last_error_at": null,
"external_id": "ART-1", "external_link": null, "asin": null, "live": null, "messages": []},
{"marketplace_id": 9, "code": "amazon", "name": "Amazon", "licensed": true, "adapter": true,
"status": 1, "upload_status": "failed", "verify_status": null, "sync_state": "in_sync",
"last_updated": "2026-10-14 11:00:00", "last_error_at": "2026-10-14 11:00:00",
"external_id": "SKU-4711", "external_link": "https://www.amazon.de/dp/B0C1234567", "asin": "B0C1234567", "live": null,
"messages": [{"code": "90220", "message": "Das Attribut brand fehlt.", "field": null}]}
]}| Champ | Signification |
|---|---|
marketplace_id | ID du canal dans cette boutique ; il peut varier d’une boutique à l’autre |
code | Identifiant stable du canal (liste ci-dessous) ; c’est à cela que vous reconnaissez le canal, et non à son nom |
name | Nom d'affichage dans la langue de la requête |
licensed | Canal activé dans la boutique : module multicanal souscrit et canal activé |
adapter | false: la boutique ne dispose d’aucune connexion pour cette entrée ; un bouton peut être présent, mais rien n’est transmis |
status | Bouton de l'onglet « Places de marché » : « 1 » est activé, « 0 » ne l'est pas. Modifiable |
upload_status | État de la transmission, tel qu’enregistré, sous forme de liste ouverte : done, pending, failed, error, inactive (Idealo : offre supprimée, données du canal conservées) ou null (jamais transmis ou offre supprimée). OTTO enregistre également ses propres états pour ses tâches de téléchargement ; il n’est pas possible de filtrer en fonction de ceux-ci. |
verify_status | Statut de vérification du canal, tel qu'enregistré (eBay, OTTO) |
sync_state | in_sync, pending, not_listed ou not_tracked, règle ci-dessous |
last_updated | Dernier horodatage de l'entrée, pas nécessairement une synchronisation ; 1970-01-01 00:00:00 = marqué pour synchronisation |
last_error_at | Heure de la dernière erreur ; après quoi la synchronisation est suspendue pendant un jour. Le message se trouve dans messages |
external_id, external_link | l'offre sur la plateforme (SellerSKU, Item-ID, ID de l'offre…) et le lien correspondant |
asin | Amazon uniquement : l’ASIN issu du lien |
live | Dans Shop 4.9, toujours null |
messages[] | les messages du canal concernant le dernier transfert sous la forme {code, message, field}, jamais la réponse brute |
sync_state | Signification |
|---|---|
in_sync | L'offre existe et l'article n'a pas été modifié depuis le dernier horodatage de l'entrée ; la même seconde est considérée comme actuelle. Cela ne signifie pas « transféré » : ce sont upload_status et messages qui indiquent si quelque chose est bien arrivé. |
pending | Offre disponible, article modifié depuis : « non à jour » dans la liste des articles |
not_listed | Pas d'offre sur ce canal |
not_tracked | Canal sans statut : Galaxus, Shöpping.at et entrées sans connexion |
Les canaux Mirakl n’ont pas besoin d’ID d’offre : là-bas, le statut compare uniquement les moments, et une ligne enregistrée n’est jamais not_listed. Le commutateur status ne s’applique pas à sync_state.
0) qui n’ont pas d’image, qui n’ont pas de GTIN (lorsque le canal en exige un), qui présentent un EAN ou un MPN en double, qui figurent sur une liste noire, qui sont bloqués en tant qu’invité ou qui ne respectent pas les restrictions de catégorie du canal. Idealo désactive les offres lorsque le stock est à 0 (0, upload_status inactive ) et les réintègre dès que le stock est réapprovisionné. La synchronisation des fiches produits (Amazon, Kaufland) s’effectue via 1, la fonction « Supprimer des produits » sur eBay via 0; à cela s’ajoutent le masque d’article, le CSV-Porter et l’importation XML.Écrire
POST/PUT /products avec la clé marketplaces active le commutateur pour chaque canal (portée products:write). Seuls les canaux envoyés changent ; si la clé est absente, tout reste inchangé. Une entrée désigne le canal via code ou marketplace_id et contient status: 1/0, true/false ou "1"/"0". Les champs de lecture de l’exportation sont ignorés : une exportation avec ?marketplaces=1 est renvoyée telle quelle, de même qu’avec ?_strict=true. L’écriture ne concerne que les canaux disposant d’une licence dans la boutique.
PUT /xpanel/xoport/Import/JSON/products
{"type": "products", "data": [{"products_id": 4711, "marketplaces": [
{"code": "idealo", "status": 1},
{"marketplace_id": 9, "status": false},
{"code": "manor", "status": "1"}
]}]}
→ "stats": {"…": "…", "marketplaces": {"processed": 1, "written": 1, "unchanged": 0, "skipped": 0}},
"records": [{"index": 1, "products_id": 4711, "status": "updated", "marketplaces": "written"}]| Avertissement | Lorsque |
|---|---|
INVALID_MARKETPLACE | marketplaces n'y a-t-il pas de liste, une entrée n'est-elle pas un objet, ou ne mentionne-t-elle pas de chaîne de cette boutique |
INVALID_MARKETPLACE_STATUS | status manque ou n’est pas 0/1 |
MARKETPLACE_NOT_LICENSED | canal sans licence, et l’entrée modifierait le commutateur enregistré |
Les avertissements n’interrompent rien : l’enregistrement et ses autres entrées continuent de fonctionner, le commutateur enregistré reste inchangé. Une entrée avec 0 pour un canal non validé ne crée rien. La réponse signale pour chaque enregistrement records[].marketplaces (written ou unchanged) et dans le lot stats.marketplaces.
- Marquage : un «
PUT» comportant uniquement le code article (products_id,products_model) etmarketplacesne modifie pas la date de modification de l’article ; il n’apparaît pas dans le delta?last_modified. Chaque ligne de canal modifiée reçoitlast_updated1970-01-01 00:00:00et synchronise précisément ce canal lors de la prochaine exécution. Avec une offre (sur Mirakl, même sans), elle affiche alorssync_statepending; sans offre, elle restenot_listed. Si l’enregistrement comporte d’autres champs modifiés, l’article est horodaté comme auparavant. - Effet sur le canal (lors de la prochaine synchronisation, pas d’appel immédiat) : sur Amazon, eBay, Kaufland et Idealo,
1crée une offre, tandis que0la supprime. Les canaux Mirakl tels que Manor ou Decathlon ne transmettent que des données Mirakl mises à jour (brand,category) et ne suppriment pas les articles. OTTO et Galaxus suivent le prochain export complet ; Shöpping.at intègre l’article lors du prochain flux, mais ne le retire pas. La boutique ne transmet pas les données relatives à Check24, Etsy et aux annonces sans connexion. - Idealo : un retrait (
0) reste valable, même si le produit est en stock. Si la boutique a elle-même désactivé une offre en raison d’un stock égal à 0, elle la réactive dès le réapprovisionnement, à condition que l’option « Réactiver automatiquement lors du réapprovisionnement » soit activée pour l’article ; l’envoi d’unstatus0n’y change rien. Une offre désactivée par le filtre, par exemple en raison de l’absence d’image, ne réapparaît pas automatiquement une fois le problème résolu : renvoyez la requêtestatus1.
PUT e réinitialise les commutateurs à l’état de l’exportation. N’envoyez donc marketplaces que si les commutateurs doivent être modifiés, ou récupérez l’exportation sans ?marketplaces=1. À l’inverse, un masque d’article ouvert puis enregistré réinitialise le commutateur à son état initial : après une validation via l’API, rechargez les masques ouverts. Et la synchronisation suivante peut annuler une valeur définie (filtres, stock Idealo) : pour plus d’informations, consultez PUT, status et upload_status.Filtres de la place de marché
« Tous les articles Idealo non à jour » en une seule requête plutôt que sur l’ensemble du catalogue : ?marketplace= indique le canal (ID ou code, liste séparée par des virgules jusqu’à 100 valeurs), auquel s’ajoute au moins une condition. ?marketplaces=1 n’est pas nécessaire pour cela. Jusqu’à la version v2.122.10, les noms étaient ignorés ( UNKNOWN_FILTER ) et la liste était renvoyée sans filtrage.
| Paramètres | Valeurs |
|---|---|
marketplace_status | 1 acceptées, 0 non (même sans ligne enregistrée) |
marketplace_upload_status | done, pending, failed, error, inactive, none (sans statut) ; liste séparée par des virgules |
marketplace_sync_state | in_sync, pending, not_listed, not_tracked; liste séparée par des virgules |
# alle nicht aktuellen Idealo-Artikel
GET /Export/JSON/products?marketplace=idealo&marketplace_sync_state=pending&limit=250
# „ausstehend“ der Artikelliste: freigegeben und aktiv
GET /Export/JSON/products?marketplace=idealo&marketplace_status=1&marketplace_sync_state=pending&status=1
# Amazon-Artikel mit fehlgeschlagenem Upload („fehlgeschlagen“ der Artikelliste)
GET /Export/JSON/products?marketplace=amazon&marketplace_upload_status=error,failed
# freigegeben, aber noch nie übertragen (ohne Upload-Status), auf einem der Mirakl-Kanäle
GET /Export/JSON/products?marketplace=manor,decathlon&marketplace_status=1&marketplace_upload_status=none- Toutes les conditions s’appliquent au même canal ; si plusieurs canaux sont mentionnés, un seul suffit. Les valeurs d’une liste constituent des alternatives.
- Le filtre «
sync_state» ne s’applique pas : «pending» trouve également un article désactivé dont l’offre est encore enregistrée. Pour les articles « en attente » comme dans la liste des articles, il faut donc ajouter «marketplace_status=1» et «status=1». - Un article sans ligne enregistrée pour le canal compte pour 0 avec
status, et pournot_listedounot_trackedavecupload_status,noneetsync_state. miraklne mentionne aucun canal ; les canaux Mirakl ont leurs propres codes.stats.totalcompte les résultats. La liste d’articles dans le backend masque en outre les articles que la synchronisation ne transfère pas, par exemple ceux sans image ou avec un code EAN en double ; ces articles figurent dans la réponse, mais pas dans la liste.
| Cas | Réponse |
|---|---|
Condition sans ?marketplace= ou ?marketplace= sans condition | HTTP 400 INVALID_FILTER_VALUE |
canal inconnu (?marketplace=idelo, ?marketplace=mirakl) | HTTP 400 INVALID_FILTER_VALUE, le message indique les codes de la boutique |
Valeur hors de la liste (?marketplace_status=2, ?marketplace_upload_status=DONE) | HTTP 400 INVALID_FILTER_VALUE, le message indique les valeurs valides |
à /products/{ID}, /product/{ID} ou une plage | HTTP 400 FILTER_NOT_APPLICABLE |
Codes
code | Canal |
|---|---|
galaxus | Galaxus |
idealo | Idealo |
otto | OTTO |
check24 | Check24 |
ebay | eBay (connexion multicanal) |
etsy | Etsy |
kaufland | Kaufland |
microspot | Microspot (uniquement les anciennes boutiques, sans connexion) |
amazon | Amazon |
shoeppingat | Shöpping.at |
manor, decathlon, conrad, mediamarkt, galeria, obi, ottooffice, home24 | Chaînes Mirakl |
Supprimer (DELETE)
Le produit est identifié par son ID dans le chemin d'accès : DELETE /xpanel/xoport/delete/json/products/{ID}. Sans ID, l'API renvoie une réponse HTTP 400 MISSING_ID.
Exemple
curl -X DELETE "https://shop.de/xpanel/xoport/delete/json/products/123" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"