API d'importation : importer des données via les méthodes POST et PUT

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

Méthodes HTTP (v2.1.0)

Sémantique RESTful pour une intention claire :

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).

Points de terminaison disponibles

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.0POST /Import/JSON/mediaPUT /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.

Format de la requête

L'importation attend un tableau data contenant les objets à importer :

{
  "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 de requête v2.122.10

Ces paramètres de requête s'appliquent à chaque point de terminaison d'importation (POST et PUT /Import/JSON/…) ; l'importation ne prend pas en charge de filtres par point de terminaison.

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é.

Format de la réponse (v2.1.0)

La réponse contient un tableau de type « records » indiquant le statut de chaque enregistrement :

{
  "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"}
  ]
}

Réponses d'erreur

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"}}

Champs supplémentaires du produit (extra_fields)

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é.

Onglets de produit (tabs)

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.

Stock par entrepôt (storages)

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.

Téléchargement de médias : champs contenant exactement une valeur v2.122.11

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

Exportation de données avec l'API xoPort

Les points de terminaison d'exportation de l'API xoPort permettent un accès en lecture à toutes les données pertinentes de la boutique en ligne. Que ce soit pour la synchronisation avec des systèmes ERP, la création de sauvegardes ou pour des analyses de Business Intelligence, l'API d'exportation fournit des données JSON cohérentes et structurées.

Optimisation des performances

  • Utilisez les exportations individuelles (/product/{id}) pour des requêtes ciblées
  • Utilisez des paramètres de filtrage pour réduire le volume de données
  • Mettez en place une mise en cache côté client
  • Pour les grands volumes de données : synchronisation incrémentielle basée sur last_modified