POST · PUT · Import · Upsert
API d'importation
Importez des données dans la boutique. Depuis la version 2.1.0, avec séparation des méthodes HTTP RESTful : POST pour les nouveaux enregistrements, PUT pour les mises à jour.
POST /Import/JSON/{entity} # INSERT
PUT /Import/JSON/{entity} # UPDATE
POST = INSERT
Créer de nouveaux enregistrements
- Crée uniquement de nouveaux enregistrements
- ID = 0 ou pas d'ID → attribution automatique d'un ID
- ID > 0 existant déjà →
409 Conflict
- Exemple : créer une nouvelle catégorie
POST /Import/JSON/categories
{"data": [{"categories_id": 0, ...}]}
PUT = UPDATE
Mettre à jour des enregistrements existants
- Met à jour uniquement les enregistrements existants
- L'ID doit exister, sinon →
404 Not Found
- aucun nouvel enregistrement n'est possible
- Exemple : modifier la description d'une catégorie
PUT /Import/JSON/categories
{"data": [{"categories_id": 142, ...}]}
Migration depuis la version 2.0.x : l'ancien comportement POST (Upsert) n'est plus pris en charge. Séparez vos importations en INSERT (POST) et UPDATE (PUT).
| Entité |
POST (INSERT) |
PUT (UPDATE) |
Portée |
| Produits |
POST /Import/JSON/products |
PUT /Import/JSON/products |
products:write |
| Catégories |
POST /Import/JSON/categories |
PUT /Import/JSON/categories |
categories:write |
| Clients |
POST /Import/JSON/customers |
PUT /Import/JSON/customers |
customers:write |
| Fabricants |
POST /Import/JSON/manufacturers |
PUT /Import/JSON/manufacturers |
manufacturers:write |
| Articles d'actualité |
POST /Import/JSON/newsdesks |
PUT /Import/JSON/newsdesks |
news:write |
| Catégories d'actualités |
POST /Import/JSON/newsdeskcats |
PUT /Import/JSON/newsdeskcats |
newscategories:write |
| FAQ |
POST /Import/JSON/faq |
PUT /Import/JSON/faq |
faq:write |
| Catégories de la FAQ |
POST /Import/JSON/faqcats |
PUT /Import/JSON/faqcats |
faqcategories:write |
| Historique SEO |
POST /Import/JSON/seohistory |
PUT /Import/JSON/seohistory |
seohistory:write |
| Contrats |
POST /Import/JSON/contracts |
PUT /Import/JSON/contracts |
contracts:write |
| Abonnés à la newsletter |
POST /Import/JSON/newsletter_subscribers |
PUT /Import/JSON/newsletter_subscribers |
newsletter:write |
| Campagnes de newsletter |
POST /Import/JSON/newsletters |
PUT /Import/JSON/newsletters |
newsletters:write |
| Tickets v2.60.0 |
POST /Import/JSON/tickets |
PUT /Import/JSON/tickets |
tickets:write |
| Commandes NOUVEAU v2.20.0 |
POST /Import/JSON/orders |
PUT /Import/JSON/orders |
orders:write |
| Médias NOUVEAU v2.16.0 | POST /Import/JSON/media | PUT /Import/JSON/media/{id} | media:write |
| Slider NOUVEAU v2.22.0 |
POST /Import/JSON/slider |
PUT /Import/JSON/slider |
slider:write |
| Projets (xoCRM) NOUVEAU v2.35.0 |
POST /Import/JSON/projects |
PUT /Import/JSON/projects |
projects:write |
| Tâches de projet NOUVEAU v2.35.0 |
POST /Import/JSON/project_tasks |
PUT /Import/JSON/project_tasks |
project_tasks:write |
| Rendez-vous (xoCRM) NOUVEAU v2.44.0 |
POST /Import/JSON/appointments |
PUT /Import/JSON/appointments |
appointments:write |
| Caractéristiques et valeurs d'options NOUVEAU v2.96.0 |
POST /Import/JSON/products_options |
PUT /Import/JSON/products_options |
products:write |
| Profils de délais de livraison NOUVEAU v2.94.0 |
POST /Import/JSON/shipping_profiles |
PUT /Import/JSON/shipping_profiles |
products:write |
| Services de tickets NOUVEAU v2.103.0 |
POST /Import/JSON/ticket_departments |
PUT /Import/JSON/ticket_departments |
tickets:write |
| Paramètres NOUVEAU v2.108.0 |
– |
PUT /Import/JSON/configuration |
configuration:write |
| Importateur CSV/XLS NOUVEAU v2.109.0 |
– |
PUT /Import/JSON/porters |
porters:write |
| Réclamations de tickets NOUVEAU v2.118.0 |
– |
PUT /Import/JSON/ticket_claims |
tickets:write |
Modification dans la v2.122.13 : nouvel avertissement «
CONTRACT_STATUS_UNKNOWN » lors de l’importation des contrats (
POST/PUT /Import/JSON/contracts) : si
status.status_id ou
contracts_status_id ne correspond pas à un statut de contrat de la boutique ou n’est pas un nombre entier, ou si l’objet
status ne contient pas la balise
status_id, l’importation enregistre l’entrée sans le statut. Elle est alors comptabilisée comme
inserted ou
updated; l’avertissement apparaît dans
warnings[] et au niveau de l’entrée dans
records[], avec
field et
contracts_id.
PUT /Import/JSON/contracts/{id}/status vérifie désormais également les statuts des contrats : un identifiant inconnu ou non entier génère une erreur HTTP 400
INVALID_STATUS;
error.valid_status_ids répertorie les identifiants valides. Jusqu’à la version v2.122.12, les deux méthodes vérifiaient les statuts des commandes ; l’importation ignorait silencieusement un statut rejeté, et
contracts_status_id était écrit sans vérification. Détails sous
Contracts.
Modification apportée dans la version v2.122.11 :POST/PUT /Import/JSON/customers enregistre le blocage de la newsletter à l’adresse customers_nl_lock (0/1, true/false, "0"/"1"). 1 désabonne tous les abonnements du compte, dans la boutique et auprès du fournisseur de newsletter ; un fichier d’exportation renvoyé avec 1 effectue également le désabonnement. Auparavant, le champ était UNKNOWN_FIELD. Détails sous Clients.- Téléchargement de médias : un champ contenant exactement une valeur sous forme de liste génère une erreur HTTP 400
INVALID_FIELD_VALUE; les erreurs de téléchargement de type multipart/form-data surviennent également en cas d’UTF-8 non valide avec troncature (section « Télécharger des médias » ci-dessous).
Nouveauté de la v2.57.0 : POST/PUT /Import/JSON/customers gère désormais les remises clients via le champ discounts[] (remise en pourcentage par catégorie ; category_id: 0 avec les sous-catégories = gamme complète ; indicateurs optionnels subcategories, qpb, option, special). Sémantique « replace-on-present » : si le champ est présent, l’ensemble des remises du client est remplacé ; un tableau vide [] supprime toutes les remises ; si le champ est absent, les remises restent inchangées. De plus, les champs customers_id_extern (numéro client WaWi) et customers_group_id (validé par rapport aux groupes de clients existants) sont modifiables, et PUT permet également d’adresser le client sans e-mail via customers_id ou customers_id_extern (gestion des remises et des données de base uniquement) – la méthode POST continue d’exiger un e-mail et une adresse.
Nouveauté de la v2.57.0 : POST/PUT /Import/JSON/products enregistre des prix spécifiques au client via le nouveau champ customers_prices[]: client via customers_id ou customers_id_extern (numéro de client WaWi), mise à jour partielle (seules les colonnes transmises sont enregistrées, par exemple customers_price, prix échelonnés products_price1..12 + products_price1..12_qty, products_qty_blocks, products_min_qty, products_max_qty), suppression via "_action": "delete", synchronisation complète via customers_prices_replace: true (en combinaison avec un fichier customers_prices: [] vide, tous les prix client du produit sont supprimés). L’exportation des produits fournit customers_prices, compatible avec les allers-retours.
Nouveauté de la v2.24.0 : PUT /Import/JSON/tickets peut désormais envoyer non seulement des commentaires internes, mais aussi des réponses publiques aux clients. Deux indicateurs booléens facultatifs par article : is_public_reply: true rend l’entrée d’historique publique (visible dans l’interface publique), notify_customer: true envoie en outre un e-mail au client via le modèle $mail_ticket_update_with_content(de la même manière que le chemin de réponse dans l’interface d’administration). Ces deux indicateurs doivent être strictement de type booléen true — toutes les autres valeurs sont ignorées. À partir de la v2.25.0 : pour is_public_reply: true, la signature de l’administrateur enregistrée dans le backend est automatiquement ajoutée (entrée d’historique + e-mail) — Désactivation via append_signature: false. Il ne faut donc plus envoyer de signature propre dans ticket_comments. admin_id > 0 est obligatoire pour les réponses publiques. Le comportement par défaut (sans indicateurs) reste « interne » et sans e-mail — rétrocompatible avec tous les clients v2.11.0+. À partir de la v2.60.0 : POST/PUT /Import/JSON/tickets acceptent désormais également attachments[] (name + content_base64) — les pièces jointes aux tickets ne sont donc plus seulement en lecture, mais également en écriture. Les fichiers sont associés à l’entrée du historique : sans ticket_comments dans la même requête, ils sont rejetés avec un message d’erreur.
Nouveauté de la v2.20.0 : POST/PUT /Import/JSON/orders permet de créer et de mettre à jour des commandes. La commande est créée selon les mêmes règles que pour les importations depuis les places de marché ; le stock est enregistré lors de la création (Best-Effort).
Nouveauté de la v2.17.0 : PUT /Import/JSON/products permet de gérer les offres spéciales liées aux produits via specials[]: plusieurs groupes de clients, prix spécial net ou discount_percent, période, statut, désignation de l’offre et suppression par groupe.
{
"type": "categories",
"data": [
{
"categories_id": 142,
"parent_id": 110,
"status": 1,
"languages": {
"de": {
"categories_name": "Produktattribute",
"categories_description": "<p>Beschreibung als HTML-String...</p>"
}
}
}
]
}
| Paramètres |
Valeurs |
Effet |
_strict |
true ou false |
Avec true, un champ inconnu interrompt l'ensemble de l'appel avec un code HTTP 400 UNKNOWN_FIELD avant que quoi que ce soit ne soit écrit. Sans ce paramètre ou avec false, l'import signale les champs inconnus sous forme d'avertissement et écrit les autres. |
language |
une valeur unique (uniquement pour la règle de liste) |
Définit la langue de session de l’importation, par exemple pour la recherche de noms des caractéristiques. La boutique n’accepte que le code exact d’une langue du backend et, dans le cas contraire, revient silencieusement à sa langue par défaut, qui est généralement celle qui était prévue. On vérifie donc uniquement qu’aucune valeur de liste n’est fournie. |
- Seul
true active le mode strict ; depuis la version v2.122.12, les espaces en fin de chaîne ne sont plus pris en compte (?_strict=%20true est considéré comme true). Dans les boutiques antérieures à la version 4.9.173, envoyez la valeur sans espace à la fin. Les autres valeurs de _strict (par exemple 1 ou yes) et toute valeur de liste (?language[]=de, ?_strict[]=true) génèrent une erreur HTTP 400 INVALID_FILTER_VALUE avec le message details[] («… Nothing was written »). La vérification s’effectue avant que l’importation ne lise ou n’écrive quoi que ce soit.
- Une valeur vide signifie « non défini » ; depuis la version v2.122.12, un simple espace vide est également accepté pour
_strict: l’importation fonctionne alors comme si le paramètre n’existait pas. L’importation continue d’ignorer les noms de paramètres inconnus.
Modifié dans la v2.122.10 : Jusqu’à la v2.122.9, ?_strict=1 omettait silencieusement le mode strict : l’appelant s’attendait à un « tout ou rien », mais l’importation écrivait parfois malgré des champs inconnus. ?language[]=de se terminait par un code HTTP 500. Les deux cas renvoient désormais un code HTTP 400, et rien n’est écrit.
Modification apportée dans la version 2.122.12 : jusqu’à la version 2.122.11, la boutique vérifiait _strict sans espace à la fin, mais l’importation lisait ensuite la valeur non tronquée. ?_strict=%20true ou ?_strict=true%0A passaient la vérification et désactivaient néanmoins silencieusement le mode strict : l’importation enregistrait partiellement les données malgré la présence de champs inconnus. Désormais, c’est la valeur vérifiée qui s’applique, et le mode strict est activé.
{
"success": true,
"api_version": "2.1.0",
"type": "categories",
"stats": {
"total": 2,
"inserted": 1,
"updated": 1,
"failed": 0
},
"errors": [],
"timestamp": "2026-01-27 14:00:00",
"records": [
{"index": 1, "categories_id": 144, "status": "inserted"},
{"index": 2, "categories_id": 142, "status": "updated"}
]
}
409 Conflit (POST)
L'ID existe déjà :
{"success":false,"error":{"code":"ALREADY_EXISTS"}}
404 Page introuvable (PUT)
L'ID n'existe pas :
{"success":false,"error":{"code":"NOT_FOUND"}}
Les champs supplémentaires d'un produit sont exclusivement définis via le champ extra_fields – et non en tant que champ de premier niveau du produit. Une clé telle que google_product_category placée juste à côté de products_model sera ignorée (et signalée dans la réponse sous la forme UNKNOWN_FIELD ).
À partir de la version 2.51.0, chacune des notations suivantes est acceptée – au choix via l’ID du champ ou le nom du champ (la casse et les espaces sont tolérés) :
{
"type": "products",
"data": [{
"products_model": "ABC-123",
"extra_fields": {
"Google Produktkategorie (xoPort)": "1234",
"Google Zustand (xoPort)": "new",
"Google Verfügbarkeit (xoPort)": "in stock"
}
// ... alternativ nach Feld-ID:
// "extra_fields": {"7": "1234", "8": "new", "9": "in stock"}
// ... oder als Liste:
// "extra_fields": [{"products_extra_fields_id": 7, "products_extra_fields_value": "1234"}]
}]
}
La structure renvoyée par l’exportation JSON (objet indexé par ID de champ) peut également être réimportée sans modification.
Champs Google Shopping
Les champs du flux Google sont des champs supplémentaires standard dotés d'identifiants fixes :
| ID du champ |
Nom du champ (par défaut) |
Flux Google |
7 |
Catégorie de produit Google (xoPort) |
g:google_product_category |
8 |
Statut Google (xoPort) |
g:condition |
9 |
Disponibilité Google (xoPort) |
g:availability |
Remarque : une chaîne vide "" vide le champ, tandis que null signifie « non fourni » (le champ reste inchangé). Un nom de champ qui n’existe pas dans la boutique en ligne est signalé dans la réponse sous forme d’avertissement EXTRA_FIELD au lieu d’être ignoré.
Les onglets de produit (par exemple « Description des prestations », « Fiche technique », « Instructions de montage ») sont gérés via le champ tabs – un tableau plat d’objets d’onglet. Un format imbriqué comme {"de": […]} n’ est pas pris en charge et est ignoré sans message d’erreur (pas d’avertissement, pas d’onglets).
{
"type": "products",
"data": [{
"products_model": "ABC-123",
"tabs": [
{
"tab_name": "Leistungsbeschreibung",
"tab_content": "<h3>Leistungsbeschreibung</h3><p>...</p>",
"language_id": 2,
"sort_order": 0
},
{
"tab_name": "Service description",
"tab_content": "<h3>Service description</h3><p>...</p>",
"language_id": 1,
"sort_order": 0
}
]
}]
}
| Champ |
Obligatoire |
Description |
tab_name |
✓ (lors de la création d'un nouvel élément) |
Titre de l'onglet, tel qu'il s'affiche sur le produit. Si l'tab_name n'est pas renseignée, l'entrée est ignorée. |
tab_content |
– |
Contenu HTML de l'onglet. |
language_id |
– |
Langue de l'entrée (1 = anglais, 2 = allemand). Si aucune langue n'est spécifiée, la langue par défaut de la boutique s'applique. |
sort_order |
– |
Classement de l’onglet dans la liste des produits (par défaut : 0). |
tab_id |
– |
Uniquement pour les mises à jour : met à jour tab_name/tab_content d’un onglet existant dans la langue indiquée. |
Comportement détaillé
- Création : toute entrée sans
tab_id crée un nouvel onglet avec sa propre tab_id – y compris par langue. Les onglets multilingues sont donc envoyés sous la forme d’une entrée par langue (voir l’exemple ci-dessus) ; le front-end n’affiche, selon le filtre de langue, que les onglets de la langue active.
- Mise à jour : avec
tab_id, l’onglet existant est mis à jour dans la langue indiquée – mais uniquement si la ligne de langue existe déjà. Une version linguistique manquante d’un onglet existant ne peut pas être créée a posteriori via l’API (pour cela, envoyez un nouvel onglet sans tab_id ).
- Pas de suppression en cas d’absence : les onglets non envoyés restent inchangés. La suppression des onglets s’effectue dans le backend au niveau du produit.
- Aller-retour d’exportation :
GET /products renvoie les onglets sous forme d’objet indexé par tab_id (y compris tab_name, tab_content, language_id, sort_order).
Attention : le champ s'appelle tab_name/tab_content – et non tabs_title/tabs_content. Les entrées comportant des fautes d'orthographe ou une structure linguistique mélangée seront rejetées sans commentaire.
Le stock, le stock de sécurité et le seuil de réapprovisionnement par entrepôt sont gérés dans l’importation de produits exclusivement via le champ storages – un tableau plat d’objets « entrepôt », chacun comportant storages_id (alias warehouse_id). Depuis la version 2.45.0, il s’agit d’une mise à jour partielle: une charge utile partielle telle que {"storages_id": 3, "products_reorder_level": 10} ne modifie que le seuil de réapprovisionnement de cet entrepôt et ne définit pas les autres colonnes de l’entrepôt sur 0.
Le système de gestion des stocks avancé doit être activé (EXTENDED_STORAGE_SYSTEM) ; sinon, le champ est ignoré et la réponse contient l'avertissement STORAGE_SYSTEM_DISABLED. Tous les champs, leur sémantique et les pièges à éviter sont décrits dans la section « Stocks et niveaux de réapprovisionnement par entrepôt » de la référence Products.
Attention : les champs « products_reorder_level » et « products_safe_quantity » au niveau supérieur du produit concernent l’entrepôt principal (la table « products »), et non un entrepôt issu de « storages » ; ils ne sont importables qu’à partir de la version 2.63.0; auparavant, ils étaient rejetés en tant que champs inconnus. «storages_id: 1 » correspond déjà au premier entrepôt supplémentaire, et non à l’entrepôt principal. Une entrée d'entrepôt sans storages_id est ignorée sans avertissement – l'importation indique alors qu'elle a réussi, bien que rien n'ait été écrit.
Pour POST /Import/JSON/media et PUT /Import/JSON/media/{id} (portée media:write), en tant que multipart/form-data et avec un corps JSON : un champ contenant exactement une valeur, sous forme de liste (champ de formulaire entity_type[]=… ou entity_type[key]=…, dans le corps JSON un tableau ou un objet, même vide) renvoie un code HTTP 400 INVALID_FIELD_VALUE avec field. Rien n’est lu ni écrit.
{
"success": false,
"api_version": "2.122.11",
"error": {
"code": "INVALID_FIELD_VALUE",
"message": "Field \"entity_id\" must be a single value, not a list (entity_id[]=… or entity_id[key]=…; in a JSON body an array or object). Send the field once.",
"field": "entity_id"
}
}
| Champs |
jusqu'à la version v2.122.10 sous forme de liste |
entity_type, file_name, file_field |
HTTP 500 INTERNAL_ERROR |
entity_id, media_id, sort, options_id |
lu comme « 1 » : entity_id[]=5 a joint l’image à l’article 1 |
overwrite, is_main |
une liste non vide s’appelait « oui », tout comme overwrite[]=0 |
subfolder, tout comme target_folder, lorsque tous les dossiers sont partagés |
le texte « Array » : l’image s’est retrouvée dans le dossier array |
file_url, file_base64 |
ce qui prête à confusion : INVALID_URL ou INVALID_BASE64 |
le champ de fichier lui-même (plusieurs fichiers sous un même nom, file[]=…) |
erreur HTTP 413 ou 400 UPLOAD_INI_SIZE |
dans une entrée de descriptions: language_code, language_id, title |
language_code: HTTP 500 uniquement après l'enregistrement (sur POST, l'image était déjà présente ; sur PUT, les titres manquaient par la suite) ; title: le texte « Array » ; language_id: rédigé en langue 1 |
- Plusieurs fichiers sont traités en plusieurs appels ; le point de terminaison n'accepte qu'un seul fichier.
- Un champ dans une entrée de
descriptions porte le même nom que son entrée sur field, par exemple descriptions[0].language_code. La vérification s'effectue avant la première écriture.
- Inchangé :
descriptions est une liste par langue ; les champs inconnus sont ignorés comme auparavant.
- Les erreurs qui répètent un champ de formulaire (
entity_type dans UNSUPPORTED_ENTITY_TYPE, target_folder, subfolder, file_url) apparaissaient auparavant sans tronc sans UTF-8 valide (HTTP 400, 403 ou 502). Désormais, elles s’affichent avec un tronc, les caractères illisibles étant remplacés par U+FFFD. Comme auparavant, rien n’est écrit.
Lancer l'importation
Créez un client OAuth2 avec les scopes « :write » pour les ressources souhaitées.
Créer un client OAuth2