Documentation
API v1

Search API — Référence

Tout ce qu'il faut pour indexer un catalogue et lancer une première recherche en quelques minutes. Base URL : https://api.heurix.fr

L'accès nécessite une clé API. Souscrivez en self-service pour en obtenir une immédiatement, ou contactez-nous pour un besoin sur mesure.

Introduction

Le Search API expose le moteur Heurix : un moteur de recherche pensé pour les catalogues techniques, où les requêtes et les fiches produits traversent le même pipeline — normalisation (casse, accents), synonymes métier, tolérance aux fautes de frappe, puis une cascade de règles qui transforme le texte en annotations structurées. C'est cette cascade qui permet à m8x20 de retrouver une fiche enregistrée « M8 x 20 — A2 ».

L'API est REST, en JSON, sur HTTPS uniquement. Toutes les réponses sont encodées en UTF-8.

Démarrage rapide

Trois appels suffisent pour une première recherche fonctionnelle.

1 Indexez vos produits (le catalogue est créé automatiquement au premier envoi) :

curl -X POST https://api.heurix.fr/v1/index/moncatalogue/items \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{
    "rulepack": "outillage",
    "items": [
      {"id": "V1", "name": "Vis tête hexagonale", "ref": "M8 x 20 — A2", "stock": 100},
      {"id": "V2", "name": "Vis tête hexagonale", "ref": "M8 x 30 — A2", "stock": 50}
    ]
  }'

2 Cherchez — les fautes de frappe sont tolérées. Modifiez les champs pour voir la commande changer :

3 Lisez la réponse — chaque résultat explique pourquoi il correspond :

{
  "query": "m8x20 inox",
  "total": 2,
  "hits": [
    {
      "product": {"id": "V1", "name": "Vis tête hexagonale", "ref": "M8 x 20 — A2", "stock": 100},
      "score": 37.5,
      "in_stock": true,
      "matched": ["annotation #VIS_M8X20", "annotation #DIAM_M8", "annotation #LONG_20"]
    }
  ]
}

Authentification

Chaque appel doit porter votre clé API dans l'en-tête Authorization :

Authorization: Bearer VOTRE_CLE

Une clé manquante ou mal formée renvoie 401 ; une clé invalide ou hors de sa portée renvoie 403.

Deux types de clés

Le choix n'est pas cosmétique : il détermine ce qu'un tiers peut faire s'il met la main sur votre clé.

TypePréfixePortéeOù l'utiliser
Clé serveur hx_ Accès complet : indexation, merchandising, statistiques, facturation Côté serveur uniquement. Jamais dans une page web.
Clé publique hxp_ Recherche, parcours de catalogue, événements de conversion — rien d'autre Navigateur, applications mobiles, tout contexte où la clé est visible

Une clé publique appelant un endpoint hors de sa portée reçoit un 403 explicite. Elle partage le quota de la clé serveur qui l'a générée — en créer plusieurs n'augmente donc pas vos plafonds.

Générer une clé publique

Depuis votre console (Mon compte → Clé API), ou par API avec votre clé serveur :

curl -X POST https://api.heurix.fr/v1/keys/public \
  -H "Authorization: Bearer VOTRE_CLE_SERVEUR" \
  -H "Content-Type: application/json" \
  -d '{"allowed_origins": "monsite.fr,www.monsite.fr"}'

allowed_origins est optionnel mais recommandé : la clé n'est alors acceptée que depuis les domaines listés, ce qui la rend inutilisable si quelqu'un la recopie sur un autre site. GET /v1/keys/public les liste, DELETE /v1/keys/public/{clé} en révoque une.

Les clés existantes restent des clés serveur et continuent de fonctionner à l'identique — rien ne casse. Mais si vous en avez posé une dans le JavaScript de votre site (barre de recherche, tracker, widget Browse), remplacez-la par une clé publique : en l'état, n'importe quel visiteur peut la lire et accéder à votre facturation.

Comptage des requêtes

La facturation repose sur un compteur mensuel de requêtes, simple par principe :

Recherche1 appel search = 1 requête
Indexationjamais décomptée du quota de requêtes — mettez à jour votre catalogue aussi souvent que nécessaire, seul le nombre total de produits indexés reste plafonné par plan
Recherche fédérée1 requête par catalogue interrogé
GestionLa plupart des appels de gestion ne sont pas décomptés — clés, comptes, abonnement, priorités de merchandising, analytics. Neuf points d'entrée font exception, listés juste en dessous
Ranking1 appel browse = 1 requête Browse, sur un compteur séparé de celui de la recherche
Dépassement (Scale)Le surplus est facturé au tarif indiqué sur la page tarifs — jamais de coupure
Dépassement (autres plans)Une marge de tolérance de 10 % absorbe un pic ponctuel ; au-delà, la requête est refusée avec un code 429 explicite

Les appels de gestion qui décomptent

Ces neuf points d'entrée comptent chacun pour une requête. Aucune règle générale ne les distingue des autres appels de gestion — ni le verbe HTTP, ni la nature de l'opération : c'est pourquoi ils sont listés un par un plutôt que résumés en une phrase.

GET /v1/index/{catalog}/statsStatistiques du catalogue
GET /v1/index/{catalog}/synonymsLecture des synonymes
PUT /v1/index/{catalog}/synonymsRemplacement des synonymes
GET /v1/index/{catalog}/synonym-suggestionsSuggestions de synonymes
GET /v1/rulepacksListe des packs de règles
PUT /v1/index/{catalog}/configChangement de pack actif
POST /v1/index/{catalog}/custom-rulesCréation d'une règle personnalisée
DELETE /v1/index/{catalog}/custom-rules/{id}Suppression d'une règle personnalisée
DELETE /v1/index/{catalog}/items/{id}Suppression d'un produit

À connaître si vous synchronisez un catalogue depuis une boutique : la suppression porte sur un produit et compte pour une requête. Retirer 300 produits un par un consomme donc 300 requêtes de votre quota mensuel.

Votre consommation en cours est consultable à tout moment via GET /v1/usage.

Plans et plafonds

Chaque clé API est rattachée à un plan qui définit son volume de requêtes mensuelles, son nombre de produits indexables, et son nombre de catalogues. Le détail des fonctionnalités de chaque plan est sur la page tarifs ; voici les plafonds bruts :

PlanRequêtes / moisProduits maxCatalogues
Essai (14 jours)2 0002 0002
Starter — 19 €/mois15 0008 0001
Growth — 49 €/mois30 00025 0003
Scale — 139 €/mois150 000 puis facturé50 000illimité

Passé les 14 jours d'essai sans souscription à un plan payant, une clé repasse automatiquement aux plafonds de l'essai jusqu'à souscription — vos catalogues et leur configuration restent intacts, simplement moins accessibles en volume le temps de passer à un plan payant.

Un dépassement de plafond renvoie un code 429 avec le détail de la cause :

{
  "detail": "Plafond de requêtes du plan 'starter' dépassé (5512/5000, marge de 10% également dépassée). Passez à un plan supérieur pour continuer.",
  "plan": "starter",
  "limit_type": "requests",
  "upgrade_url": "https://heurix.fr/pricing.html"
}

Structure des produits

Un produit est un objet JSON libre. Quatre champs ont un rôle particulier :

ChampTypeRôle
idstring requisIdentifiant unique dans le catalogue. Ré-indexer le même id remplace le produit (upsert).
refstringRéférence produit (SKU, code fabricant). Indexée, poids le plus fort.
namestringNom du produit. Indexé, poids fort.
descriptionstringDescription. Indexée, poids standard.
stocknumber | bool | stringDisponibilité — utilisée pour le tri à pertinence égale. 0, false, "rupture" = indisponible.
lat / lonnumber | stringPosition, en degrés décimaux. Envoyés comme n'importe quel autre champ — aucune migration, aucune réindexation : un produit qui les reçoit au prochain envoi est géolocalisé le jour même. Les chaînes sont acceptées ("44.84" vaut 44.84) ; lat sans lon ne vaut rien — une position est un couple. Utilisés par la recherche par rayon, jamais indexés comme du texte.

Tout autre champ (prix, image, URL, catégorie...) est stocké tel quel et restitué dans les résultats, sans être indexé. Un champ mérite une mention à part : compare_at_price — s'il est présent et supérieur à price, le widget de démo l'affiche automatiquement en prix barré avec le pourcentage de réduction, sans configuration supplémentaire.

POST/v1/index/{catalog}/search-overrides

Priorités de requête

Épinglez ou reléguez un produit quand une recherche contient un mot précis — « si la recherche contient promo, tel produit apparaît en premier ». Indépendant de Browse & Discovery, qui classe des catégories de navigation, pas des requêtes de recherche.

curl -X POST https://api.heurix.fr/v1/index/moncatalogue/search-overrides \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"query": "promo", "product_id": "sku-123", "action": "pin", "position": 1}'

Déclenchement par sous-séquence contiguë, pas seulement une correspondance exacte : une règle posée sur "promo" se déclenche pour "vis promo" ou "grosse promo inox", mais pas pour "promotionnel" (mot différent une fois normalisé). Un pin peut faire apparaître un produit qui ne matche pas textuellement la requête — c'est le point de cette fonctionnalité, pas un bug.

ChampTypeDescription
querystringLe déclencheur, normalisé comme le reste du moteur (accents, casse) avant comparaison.
product_idstringLe produit concerné.
actionstringpin (position exacte, requiert position) ou bury (fin de liste, ne s'applique qu'à un produit déjà présent naturellement).
positionentierLe rang final du produit, à partir de 1. position: 3 le place en 3e position ; les autres produits comblent les places restées libres dans leur ordre naturel. Deux produits demandant le même rang sont placés côte à côte. Un rang supérieur au nombre de résultats ramène le produit en fin de liste.

GET /v1/index/{catalog}/search-overrides liste toutes les règles du catalogue (ou celles d'une requête précise avec ?query=...) ; DELETE /v1/index/{catalog}/search-overrides?query=...&product_id=... en retire une. Aucun de ces trois appels ne consomme le quota — ce sont des actions de configuration, pas des requêtes de recherche.

Facettes et filtres

Une facette regroupe les annotations qui partagent un même préfixe — FORMAT_POCHE et FORMAT_BROCHE appartiennent au groupe FORMAT. Le groupe est le préfixe, pas un nom déclaré : le moteur retient les annotations qui commencent par FORMAT_. Demandez un décompte par groupe avec facets, restreignez les résultats avec filters :

curl -X POST https://api.heurix.fr/v1/index/moncatalogue/search \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -d '{"q": "polar", "facets": ["FORMAT", "LANG"], "filters": ["FORMAT_POCHE"]}'
{
  "total": 17,
  "hits": [ ... ],
  "facets": {
    "FORMAT": {"FORMAT_POCHE": 17, "FORMAT_BROCHE": 9, "FORMAT_GF": 4},
    "LANG":   {"LANG_FR": 12, "LANG_EN": 5}
  }
}

Réponse relevée sur le moteur, pack livres, catalogue de 44 fiches dont 30 polars — 12 poche en français, 9 brochés en français, 5 poche en anglais, 4 grand format en français. Les cinq annotations citées ici sont celles que le pack pose vraiment ; aucune n'est un exemple de forme.

Le décompte et le filtre ne se comportent pas pareil, et c'est la distinction à retenir. Le décompte est disjonctif ; le filtre est cumulatif. Les deux répondent à deux questions différentes.

  • Le décompte est disjonctif au sein d'un même groupe : filtrer sur FORMAT_POCHE ne fait pas disparaître FORMAT_BROCHE du décompte. C'est ce qui permet d'afficher « Broché (9) » comme option alternative même une fois le poche sélectionné, exactement comme un filtre de boutique en ligne classique. Il répond à : « combien y en aurait-il si je cochais celle-ci ? »
  • Entre groupes, le filtre s'applique — c'est pourquoi LANG affiche 12 et 5 au lieu de 25 et 5 : les langues comptées sont celles des seuls polars au format poche. La disjonction ne vaut qu'à l'intérieur du groupe filtré.
  • Le filtre est cumulatif partout — entre deux groupes différents comme à l'intérieur d'un même groupe. filters: ["FORMAT_POCHE", "FORMAT_BROCHE"] demande les produits qui portent les deux annotations, pas l'une ou l'autre : sur un catalogue où un livre n'a qu'un format, cela rend zéro résultat, pendant que le décompte continue d'afficher 17 et 9. Mesuré sur le même catalogue. Il répond à : « lesquels satisfont tout ce qui est coché ? »

Ce n'est donc pas une contradiction, mais il faut l'avoir en tête avant de câbler une barre latérale à cases à cocher : cocher deux valeurs d'un même groupe vide la liste alors que les compteurs affichent des nombres positifs.

Pour exprimer un OU, côté Browse uniquement : filters=langue:fr|en — le tuyau sépare des alternatives à l'intérieur d'un champ métier, celui que vous fournissez à l'indexation, la virgule sépare des champs qui restent cumulatifs. Voir Browse. Côté recherche, le tuyau vaut pour les filtres de champfilters: ["brand:Makita|Bosch"] rend l'une ou l'autre. Les annotations, elles, restent toujours cumulatives entre elles : ["FORMAT_POCHE", "FORMAT_BROCHE"] exige les deux, et il n'existe pas de syntaxe pour en demander une seule.

GET/v1/browse/{catalog}/{category}

Browse & Discovery

Classe tous les produits d'une catégorie sans requête — pour une page de listing (catégorie, rayon), pas une barre de recherche. Remplace un tri statique par un classement configurable, enrichi par vos propres priorités manuelles.

{category} est un champ que vous fournissez à l'indexation — exactement comme name ou stock, jamais dérivé d'un pack de règles. Ajoutez categories (une liste — un produit peut appartenir à plusieurs catégories à la fois, utile pour lister aussi bien la catégorie que ses ancêtres) ou category (une valeur unique) à vos produits :

Pas envie d'écrire l'appel fetch vous-même ? Téléchargez le snippet prêt à l'emploi — un guide pas à pas avec exemple complet est disponible sur le blog.

curl -X POST https://api.heurix.fr/v1/index/moncatalogue/items \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"id": "sku-123", "name": "Perceuse GDX 18V-285",
       "categories": ["outillage-electroportatif", "perceuses-visseuses"],
       "price": 139.90, "margin": 28.50, "stock": 8}]}'

curl "https://api.heurix.fr/v1/browse/moncatalogue/perceuses-visseuses?sort=price_asc" \
  -H "Authorization: Bearer VOTRE_CLE_API"
ParamètreTypeDescription
sortstringstock (défaut) · recent · alphabetical · price_asc / price_desc (lit le champ de prix de la clé appelante, price par défaut — voir Prix par clé publique) · margin (lit le champ margin, la plus forte d'abord) · popular (voir ci-dessous)
filtersstring"champ:valeur,champ2:valeur2" — affine sur n'importe quel attribut libre du produit (marque, couleur…), en plus de la catégorie. Tout champ texte non réservé devient automatiquement filtrable dès l'indexation, sans configuration.
La virgule est un ET, le tuyau est un OU. marque:Makita|Bosch rend l'une ou l'autre ; marque:Makita,couleur:bleu exige les deux. Deux paires du même champ restent cumulatives — tags:promo,tags:destockage ne rend que les produits portant les deux étiquettes, ce qui a un sens sur un champ à valeurs multiples.
facetsstring"champ,champ2" — renvoie un décompte par valeur pour chaque champ demandé, disjonctif (filtrer sur une valeur d'un champ ne fait pas disparaître les autres valeurs de ce même champ du décompte, comme côté recherche).
limit / offsetintPagination, comme pour la recherche.
in_stock_onlyboolExclut les produits en rupture (défaut false), avant le calcul des facettes — un produit masqué ne pèse pas dans les décomptes.
langstringMême filtre que côté recherche — voir Recherche.
curl "https://api.heurix.fr/v1/browse/moncatalogue/perceuses-visseuses?filters=brand:Makita&facets=brand,color" \
  -H "Authorization: Bearer VOTRE_CLE_API"

Un produit sans le champ demandé (pas de price pour un tri price_asc, par exemple) n'est jamais exclu ni ne fait planter l'appel — il se retrouve simplement en fin de liste, quel que soit le sens du tri.

Consomme le quota Browse, sur un compteur séparé de celui de la recherche — un appel de rayon ne rogne jamais votre volume de recherche.

Construire une page de rayon côté navigateur

browse accepte une clé publique (hxp_) : vous pouvez donc bâtir une page de catégorie entièrement en JavaScript, sans proxy serveur. C'est ce que fait la boutique de démonstration, dont le code est lisible dans demo-boutique.js.

// Cle PUBLIQUE (hxp_), restreinte a vos domaines via allowed_origins.
// Une cle serveur ici serait lisible par n'importe quel visiteur.
const CLE = "hxp_votre_cle_publique";
const CATALOGUE = "moncatalogue";

fetch(`https://api.heurix.fr/v1/browse/${CATALOGUE}/visserie?sort=popularity&limit=8`, {
  headers: { Authorization: "Bearer " + CLE },
})
  .then(r => r.ok ? r.json() : Promise.reject(new Error("HTTP " + r.status)))
  .then(d => {
    // browse renvoie des hits : le produit est dans h.product.
    document.getElementById("grille").innerHTML =
      (d.hits || []).map(h => renduFiche(h.product)).join("");
  })
  .catch(e => {
    // Message de VITRINE pour le visiteur, detail technique en console.
    document.getElementById("grille").textContent = "Rayon momentanement indisponible.";
    console.error("Heurix browse:", e.message);
  });

d.total donne le nombre de références du rayon, utile pour un compteur ou une pagination. Un 403 depuis un domaine non listé dans allowed_origins porte un message explicite nommant les domaines autorisés — c'est une aide au diagnostic, pas une fuite.

Tri par popularité (clics + achats)

sort=popular s'appuie sur les événements réellement remontés par le Heurix Tracker — un achat compte pour 5, un clic sur un résultat de recherche pour 1. Un produit très cliqué mais jamais encore acheté reste donc visible dans le classement plutôt que totalement absent tant qu'aucune vente n'est remontée. Sans aucune donnée de tracking, tous les produits reviennent simplement dans l'ordre par défaut — l'appel ne plante jamais.

Merchandising manuel : épingler ou reléguer un produit

Indépendant du tri naturel : un produit épinglé (pin) apparaît à la position choisie en tête de liste quel que soit son stock, son prix ou sa popularité ; un produit relégué (bury) passe systématiquement en fin de liste. Utile pour mettre en avant une nouveauté ou masquer discrètement un produit en fin de vie sans le désindexer.

curl -X POST https://api.heurix.fr/v1/browse/moncatalogue/perceuses-visseuses/overrides \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"product_id": "sku-123", "action": "pin", "position": 1}'

curl -X DELETE https://api.heurix.fr/v1/browse/moncatalogue/perceuses-visseuses/overrides/sku-123 \
  -H "Authorization: Bearer VOTRE_CLE_API"

Ne consomme pas le quota — c'est une action de configuration, pas une requête. Une priorité posée sur un produit qui sort ensuite de la catégorie devient simplement sans effet, pas une erreur.

Boost ou relégation par attribut

Même principe que le merchandising manuel, mais pour tous les produits partageant un attribut plutôt qu'un par un — « boostez toute la marque Makita » plutôt qu'épingler chaque référence individuellement. Un boost ou une relégation par attribut positionne les produits concernés comme un groupe, trié entre eux selon le sort choisi ; ce n'est pas une position fixe (voir pin ci-dessus pour ça).

curl -X POST https://api.heurix.fr/v1/browse/moncatalogue/perceuses-visseuses/attribute-rules \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"field": "brand", "value": "Makita", "action": "boost"}'

curl -X DELETE "https://api.heurix.fr/v1/browse/moncatalogue/perceuses-visseuses/attribute-rules?field=brand&value=Makita" \
  -H "Authorization: Bearer VOTRE_CLE_API"

Ordre de priorité en cas de conflit, du plus fort au plus faible : épinglé (par produit) > relégué (par produit ou par attribut) > boosté (par attribut) > tri naturel. Une relégation, qu'elle soit posée par produit ou par attribut, l'emporte toujours sur un boost — un produit explicitement caché ne doit jamais être resurfacé par une règle de boost plus large qui le concernerait par ailleurs.

POST/v1/federated-search

Recherche fédérée

Interroge plusieurs catalogues en un seul appel et fusionne les résultats par pertinence — utile pour un site multilingue (un catalogue par langue) ou multi-marque. Les poids de score sont globaux au moteur, pas propres à un catalogue : les scores restent comparables entre catalogues, même avec des packs de règles différents.

Corps de la requête

ParamètreTypeDescription
catalogsarray de string requis1 à 10 noms de catalogues à interroger.
qstringLa requête de recherche.
limit / offsetintegerPagination sur l'ensemble fusionné.
filters / facetsarray de stringMêmes formes que sur la recherche — annotations et champs métier mélangés. Le filtre s'applique catalogue par catalogue ; les décomptes de facettes sont sommés entre catalogues, parce qu'une même valeur dans deux boutiques désigne la même chose.
lat / lon / radius_kmnumberMêmes règles et mêmes bornes que sur la recherche, y compris le plafond de 200 km et l'exclusion des produits sans position.

Réponse

Identique à une recherche classique, avec un champ catalog ajouté à chaque hit pour identifier sa provenance, et catalogs_searched / catalogs_not_found pour repérer un nom de catalogue invalide.

Les deux clés de diagnostic sont ici par catalogue, parce qu'un champ présent dans l'un et absent de l'autre est le cas normal d'une fédération, pas l'exception. filters_unknown devient un objet — {"agences": [], "produits": ["departement"]}, les catalogues sans problème listés avec un tableau vide pour que vous lisiez d'un coup où votre filtre a porté — et radius_no_positions une liste des catalogues sans aucune coordonnée. Toutes deux restent absentes quand elles n'ont rien à dire.

Un filtre qui ne désigne rien dans un catalogue en écarte tous les résultats, sans écarter le catalogue de catalogs_searched. C'est ce que ces clés rendent lisible : la réponse dit avoir cherché dans trois catalogues et n'en sert qu'un, et vous savez pourquoi.

curl -X POST https://api.heurix.fr/v1/federated-search \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -d '{"catalogs": ["boutique-fr", "boutique-en"], "q": "pull rouge"}'

Comptage : 1 requête par catalogue effectivement interrogé — une fédération sur 3 catalogues compte pour 3 requêtes.

POST/v1/index/{catalog}/items

Indexation

Ajoute ou met à jour des produits par lot (upsert sur id). Le catalogue est créé automatiquement s'il n'existe pas. Champ optionnel featured (booléen) : marque un produit comme candidat au secours affiché quand une recherche ne trouve rien — voir Recherche, secours sur zéro résultat.

Regrouper les résultats par famille

Sur un catalogue technique, une recherche large renvoie souvent des centaines de produits qui ne diffèrent que par une dimension. Mesuré sur un catalogue de 10 000 références : vis M8 inox renvoie 6 582 résultats, dont les premiers sont des M8×30, M8×80, M8×35, M8×6.

Regroupés, ces 6 582 résultats deviennent 52 familles — « Vis hexagonale inox A2, 265 produits » — classées par pertinence.

curl -X POST https://api.heurix.fr/v1/index/moncatalogue/search \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"q": "vis M8 inox", "limit": 6, "group_by": "auto"}'
{
  "total": 6582,
  "familles": 52,
  "groupes": [
    {
      "famille": "vis inox hex",
      "etiquettes": ["FAM_VIS", "MAT_INOX", "TETE_HEX"],
      "produits": 265,
      "score": 60.0,
      "representant": { "id": "REF-001360", "name": "Vis hexagonale M8x30 inox A4" },
      "ids": ["REF-001360", "REF-001789"]
    }
  ]
}
ParamètreEffet
group_by: "auto"Regroupe sur les étiquettes standards du pack — famille, matière, type de tête, coloris
group_by: "FAM,MAT"Regroupe sur les préfixes que vous choisissez

Les dimensions sont volontairement exclues de la clé de famille : ce sont elles qui varient à l'intérieur d'une famille. Les inclure produirait une famille par produit.

Les familles sont ordonnées par le meilleur score de leurs membres, pas par leur nombre. Une famille pertinente mais peu fournie passe donc devant une famille fournie mais marginale.

Sans group_by, la réponse est inchangée : vos intégrations existantes ne voient aucune différence.

Dans le widget JavaScript

Le regroupement se configure par un seuil, pas par un interrupteur. Un visiteur qui tape une référence précise veut son produit, pas une famille — grouper systématiquement transformerait une recherche exacte en détour.

Heurix.searchBox({
  apiKey: "hxp_votre_cle_publique",
  catalog: "moncatalogue",
  containerId: "ma-recherche",

  // Regroupe uniquement au-dela de 50 resultats
  groupThreshold: 50,

  // Optionnel : que faire au clic sur une famille.
  // Par defaut, la recherche s'affine avec le nom de la famille.
  onSelectGroup: function (famille, requete) {
    window.location = "/recherche?q=" + encodeURIComponent(requete + " " + famille.famille);
  }
});

Trois niveaux d'intégration

Le temps d'intégration ne dépend pas de Heurix mais de ce que vous remplacez. Ces trois niveaux vont du plus léger au plus complet — rien n'oblige à commencer par le dernier.

NiveauCe que vous remplacezOrdre de grandeur
1 — Autocomplétion Le menu déroulant de votre barre de recherche 1 jour
2 — Liste d'identifiants Quels produits et dans quel ordre. Votre gabarit, vos fiches et vos facettes restent les vôtres 3 à 5 jours
3 — Remplacement complet Toute la page de résultats, avec nos facettes et notre analytique 2 à 3 semaines

Niveau 2 — le paramètre ids_only

Heurix décide quels produits et dans quel ordre. Votre plateforme affiche ses propres fiches. La réponse ne contient que l'essentiel — sur 50 résultats, quelques centaines d'octets au lieu de plusieurs dizaines de kilo-octets.

curl -X POST https://api.heurix.fr/v1/index/moncatalogue/search \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"q": "M8x20 inox", "limit": 24, "ids_only": true}'

# {"total": 47, "ids": ["SKU-1234", "SKU-5678", ...]}

Le classement, la pagination et les filtres s'appliquent exactement comme sur la réponse complète. Seule la forme change.

Appliquer cet ordre dans Magento

Sur une page de catégorie, la fonction FIELD() de MySQL impose l'ordre exact d'une liste à une collection de produits :

// Plugin sur la collection de produits
$ids = $this->heurix->getOrderedIds($query);   // appel ids_only

$collection->addIdFilter($ids);
$collection->getSelect()->order(
    new \Zend_Db_Expr('FIELD(e.entity_id,' . implode(',', $ids) . ')')
);

Point de vigilance. Depuis Magento 2.4, la page de résultats de recherche ne passe plus par une collection SQL : elle transite par Elasticsearch ou OpenSearch. Un plugin de collection n'y produit donc aucun effet, et il faut passer par un adaptateur de recherche personnalisé. Les pages de catégorie, elles, restent en collection SQL — c'est pourquoi l'option Ranking s'intègre nettement plus vite que Search.

Selon votre plateforme

Toutes n'accueillent pas une liste d'identifiants avec la même facilité. L'écart est important, et il détermine votre temps d'intégration bien plus que Heurix lui-même.

PlateformePoint d'accrocheNatureOrdre de grandeur
PrestaShop 1.7+ ProductSearchProviderInterface Interface prévue pour cela. Le cœur accepte un tableau d'identifiants et hydrate fiches, facettes, pagination et tri. 2 à 3 jours
WooCommerce pre_get_posts avec post__in et orderby => post__in Détournement de la requête WordPress. Fonctionne, mais ce n'est pas un point d'extension conçu pour cela. 3 à 4 jours
Magento 2.4+ — pages de catégorie FIELD(e.entity_id, …) sur la collection Fonctionne : la collection reste en SQL. 3 à 5 jours
Magento 2.4+ — page de résultats Adaptateur de recherche personnalisé La recherche transite par Elasticsearch : un plugin de collection n'y produit aucun effet. 2 à 3 semaines
Shopify Non vérifié à ce jour. Écrivez-nous plutôt que de suivre une recette approximative.

PrestaShop — module officiel

Un module prêt à installer existe. Il implémente ProductSearchProviderInterface et n'exige aucune modification de votre thème. Il est en bêta, fourni sur demande : écrivez-nous pour recevoir le dossier de l'étape 1.

CompatibilitéPrestaShop 8.x et 9.x. La branche 1.7 n'est pas prise en charge : le module refuse de s'installer en deçà de 8.0.0
DépendancesAucune — cURL natif
Ce qu'il remplaceLes résultats de recherche uniquement. Thème, gabarits et filtres restent les vôtres
RepliRecherche native de PrestaShop si Heurix ne répond pas
Installation
  1. Copiez le dossier heurixsearch dans modules/ de votre boutique
  2. Back-office → Modules → installer « Heurix Search »
  3. Renseignez votre clé serveur (hx_) et le nom de votre catalogue
  4. Activez
Un prérequis à connaître

PrestaShop identifie ses produits par un entier (id_product). Votre catalogue Heurix doit donc être indexé avec ces identifiants, et non avec vos références internes.

Concrètement, le champ id envoyé à Heurix doit contenir 1360, pas REF-001360. Vous pouvez conserver votre référence métier dans le champ ref, qui est indexé avec le poids le plus fort.

{
  "items": [
    {
      "id": "1360",                        // id_product PrestaShop
      "ref": "REF-001360",                 // votre reference metier
      "name": "Vis tete hexagonale M8x20 inox A2",
      "price": 1.24,
      "stock": 2485
    }
  ]
}

Si vos identifiants ne sont pas numériques, le module les écarte et l'inscrit dans les journaux PrestaShop — vous verrez alors « aucun résultat » alors que Heurix en a trouvé. C'est le symptôme à reconnaître.

PrestaShop — l'interface, si vous préférez votre propre module

Un module implémentant ProductSearchProviderInterface suffit. Il reçoit la requête, appelle Heurix avec ids_only, renvoie les identifiants. PrestaShop fait le reste.

public function runQuery(ProductSearchContext $context, ProductSearchQuery $query)
{
    $ids = $this->heurix->rechercher($query->getSearchString());   // ids_only

    $resultat = new ProductSearchResult();
    // Le coeur de PrestaShop complete les donnees manquantes
    $resultat->setProducts(array_map(fn($id) => ['id_product' => $id], $ids));
    $resultat->setTotalProductsCount(count($ids));
    return $resultat;
}

WooCommerce — un détail à connaître

Le mécanisme fonctionne, mais WordPress continue sa propre recherche tant que le paramètre s reste défini. Il faut le retirer — ce qui vide au passage le libellé « Résultats pour : … » affiché en haut de page. Prévoyez un filtre sur le titre pour le rétablir.

add_action('pre_get_posts', function ($query) {
    if (!is_admin() && $query->is_main_query() && $query->is_search()) {
        $ids = heurix_rechercher(get_search_query());   // ids_only
        $query->set('post_type', 'product');
        $query->set('post__in', $ids ?: [0]);           // [0] = aucun resultat
        $query->set('orderby', 'post__in');             // preserve NOTRE ordre
        $query->set('s', '');                           // sinon WP refiltre
    }
});

Importer un fichier depuis la console

Si vous exportez votre catalogue en CSV ou en XML depuis un ERP ou un PIM, aucun développement n'est nécessaire. La console détecte le format, analyse le fichier et l'envoie pour vous.

Formats acceptésCSV et XML, détectés automatiquement — un seul bouton de dépôt
Détection — CSVSéparateur (;, ,, tabulation) et encodage, y compris Latin-1, fréquent sur les exports français
Détection — XMLL'élément qui se répète une fois par produit, reconnu automatiquement ; encodage lu depuis le fichier lui-même. Les espaces de noms (flux Google Shopping, g:id, g:price) sont gérés sans configuration
CorrespondanceProposée d'après vos en-têtes de colonnes (CSV) ou vos éléments et attributs (XML), modifiable, avec aperçu des valeurs
Nombres1,24 et 1 234,56 sont compris ; en XML, in stock / out of stock sont reconnus pour le stock
DécoupageAutomatique en lots de 5 000
Pack de règlesRecommandé d'après le contenu de votre fichier, avant l'import

Un contrôle affiche, avant l'envoi, combien de lignes seraient retenues — une correspondance fausse se voit immédiatement plutôt qu'après coup.

Un point à décider avant votre premier import

L'identifiant que vous indexez doit être celui que vous utiliserez partout ailleurs. C'est la seule décision difficile à revenir en arrière.

Trois systèmes s'appuient sur cet identifiant :

  • Le tracker de conversion. Il enregistre le product_id que votre site lui envoie. Si vous indexez par référence métier — VIS-M8-INOX — mais que votre site envoie l'identifiant de sa plateforme — 1360 —, les deux ne se rencontreront jamais et vos analyses de ventes resteront vides.
  • Les modules de plateforme. PrestaShop, WooCommerce et Magento identifient leurs produits par un entier. Le paramètre id_field permet de garder votre référence métier comme identifiant tout en fournissant cet entier — voir ci-dessous.
  • Le serveur MCP. Il renvoie vos identifiants tels quels. Une référence métier y est même plus lisible pour un agent conversationnel.

La solution la plus souple : indexez avec votre référence métier — c'est elle que vos acheteurs tapent, et elle est fortement pondérée au classement — et ajoutez un champ platform_id (une colonne en CSV, un élément ou attribut en XML) contenant l'identifiant de votre plateforme. Vous disposez alors des deux.

Code Article;id_prestashop;Désignation;Prix HT;Qté dispo
VIS-M8-INOX;1360;Vis tête hexagonale M8x20 inox A2;1,24;2485

Dans l'écran de correspondance, associez Code Article à Identifiant et id_prestashop à Identifiant plateforme. En XML, le principe est identique : associez l'élément ou l'attribut correspondant, quel que soit son nom dans votre flux.

Catalogues volumineux : indexez par lots

Un appel accepte 5 000 produits au maximum. Au-delà, la réponse est un 422 qui vous indique en combien d'envois découper.

Les appels successifs s'ajoutent au même catalogue : l'ordre n'a pas d'importance, et rien n'est écrasé. Un produit déjà présent est mis à jour, pas dupliqué.

# Découper un catalogue de 10 000 produits en deux lots
python3 -c "
import json
produits = json.load(open('catalogue.json'))['items']
for i in range(0, len(produits), 5000):
    with open(f'lot-{i//5000}.json', 'w') as f:
        json.dump({'rulepack': 'outillage', 'items': produits[i:i+5000]}, f)
"

# Puis envoyer chaque lot
for lot in lot-*.json; do
  curl -X POST https://api.heurix.fr/v1/index/moncatalogue/items \
    -H "Authorization: Bearer VOTRE_CLE_API" \
    -H "Content-Type: application/json" \
    --data-binary @$lot
done

Ordre de grandeur. Comptez environ une seconde par tranche de 10 000 produits pour l'indexation elle-même. L'envoi réseau dépasse généralement ce temps.

Changer le pack de règles d'un catalogue existant

Le pack se déclare au moment de l'indexation, dans le champ rulepack. Il n'existe pas d'endpoint pour le changer après coup, et ce n'est pas un oubli : les annotations sont calculées à l'indexation, pas à la recherche. Changer le pack sans réindexer laisserait vos produits avec les annotations de l'ancien.

Pour changer de pack, réimportez votre catalogue avec le nouveau nom de pack. Les produits existants sont mis à jour, rien n'est perdu.

Comptez environ 6 secondes pour 10 000 produits, une minute pour 100 000. Pendant ce temps, les recherches sur l'ensemble de votre instance sont mises en attente — choisissez une heure creuse pour un gros catalogue.

curl -X POST https://api.heurix.fr/v1/index/moncatalogue/items \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{
    "rulepack": "mode",
    "items": [ ... vos produits ... ]
  }'

Corps de la requête

ParamètreTypeDescription
itemsarray requis1 à 5 000 produits par appel. Chaque produit doit avoir un id.
rulepackstringPack de règles à associer au catalogue — onze disponibles, voir la liste complète (outillage, mode, industrie, etc.). Changer de pack ré-indexe le catalogue.

Réponse

{"indexed": 2, "catalog": {"catalog": "moncatalogue", "products": 2, "terms": 11,
 "annotations": 9, "rulepack": "outillage", "synonym_groups": 0}}
DELETE/v1/index/{catalog}/items/{id}

Suppression

Retire un produit de l'index et du stockage. Renvoie 404 si l'identifiant est inconnu.

curl -X DELETE https://api.heurix.fr/v1/index/moncatalogue/items/V1 \
  -H "Authorization: Bearer VOTRE_CLE_API"
GETPUT/v1/index/{catalog}/synonyms

Synonymes

Les synonymes métier étendent la recherche : « vis » peut retrouver « boulon ». GET lit les groupes en place, PUT les remplace intégralement. Les groupes qui partagent un terme sont fusionnés automatiquement.

curl -X PUT https://api.heurix.fr/v1/index/moncatalogue/synonyms \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"groups": [["vis", "boulon", "visserie"], ["ecrou", "nut"]]}'
GET/v1/index/{catalog}/synonym-suggestions

Suggestions de synonymes

Propose des rapprochements pour un terme qui n'a rien trouvé, à partir du vocabulaire réel de votre catalogue — même distance d'édition que la tolérance aux fautes de la recherche elle-même, jamais une IA. Les jetons déjà trouvés, les références chiffrées (rapprocher deux produits différents serait le pire faux synonyme possible) et les mots de moins de 4 lettres sont exclus d'office. Ne crée jamais rien automatiquement — vous validez chaque candidat avant qu'il n'atterrisse dans l'endpoint synonymes.

curl -X GET "https://api.heurix.fr/v1/index/moncatalogue/synonym-suggestions?q=rondele" \
  -H "Authorization: Bearer VOTRE_CLE_API"

→ {"query": "rondele", "suggestions": [
     {"jeton": "rondele", "candidats": [
       {"terme": "rondelle", "distance": 1, "produits": 340}
     ]}
   ]}
GET/v1/index/{catalog}/stats

Statistiques

État du catalogue : nombre de produits, de termes indexés, d'annotations produites par la cascade, pack de règles actif, groupes de synonymes.

GET/v1/usage

Consommation

Compteur de requêtes du mois en cours pour la clé appelante.

{"month": "2026-07", "requests": 1284}
GET/v1/rulepacks

Packs de règles

Liste les packs disponibles sur votre instance, avec leur nombre de niveaux et de règles. Onze packs standards sont fournis, couvrant les verticales où les références sont structurées : outillage (visserie, normes DIN/ISO, matières), automobile (références OEM Bosch/MANN/Valeo, motorisation, côté de montage), électricité (calibres, courbes, sections, désignations harmonisées), plomberie (filetages 15x21, diamètres nominaux, matières), industrie (roulements, références normalisées), électronique, mode (tailles, coloris, matières), vins, livres, finance et sport (volumes en litres, tailles normalisées, plans de cordage). Des packs sur mesure pour votre catalogue peuvent être conçus en mission Simulateur ROI.

GET/v1/index/{catalog}/rulepack-suggestion

Suggestion de pack automatique

Recommande le pack le plus adapté d'après le contenu réel de votre catalogue, pas le secteur déclaré à l'inscription — un catalogue de vêtements resté sur le pack outillage ne le sait jamais tout seul autrement. Annote un échantillon du catalogue avec chaque pack disponible et compare la couverture obtenue. Ne modifie jamais rien : la suggestion est renvoyée, changer de pack reste une action volontaire via l'endpoint d'indexation. Ne compte pas dans le quota — une consultation de compte, comme /v1/usage.

curl -X GET https://api.heurix.fr/v1/index/moncatalogue/rulepack-suggestion \
  -H "Authorization: Bearer VOTRE_CLE_API"

→ {"classement": [
     {"pack": "mode", "annotations_distinctes": 42, "produits_annotes": 380, "couverture_pct": 95.0},
     {"pack": "outillage", "annotations_distinctes": 3, "produits_annotes": 12, "couverture_pct": 3.0}
   ], "recommande": "mode", "raison": "...",
   "marge": {"second": "outillage", "produits_annotes": 31.67,
             "annotations_distinctes": 14.0, "critere": "produits_annotes"}}

De combien le premier gagne, et quand il ne gagne pas

recommande peut valoir null, et c'est un résultat, pas une panne : aucun pack ne reconnaît vos références de façon significative, le pack déjà en place est le meilleur, l'écart avec lui est trop faible pour justifier une réindexation, ou deux packs sont à égalité. raison dit lequel de ces cas s'applique. Un client qui affiche la recommandation doit prévoir cette valeur nulle plutôt que de la traiter comme une absence de réponse.

marge dit de combien le premier devance son meilleur concurrent. second n'est pas le deuxième du classement : c'est le meilleur pack qui reconnaît quelque chose — comparer à un pack qui n'annote rien rendrait une division par zéro. Sans concurrent, les trois champs valent null ; un premier sans rival n'a pas de marge, et ce n'est pas la même chose qu'une marge nulle.

Le classement se fait sur deux critères dans cet ordre — produits_annotes d'abord, annotations_distinctes pour départager — et les deux ratios sont donnés parce qu'aucun des deux ne suffit seul. critere est la seule autorité sur l'égalité : les ratios sont arrondis pour l'affichage, critere est calculé sur les entiers. Quand il vaut null, les deux packs annotent exactement la même chose, le tri n'a rien départagé — le moteur refuse alors de recommander, et recommande vaut null lui aussi. Aucun seuil n'est appliqué sur la marge elle-même : le nombre vous est rendu, la décision reste la vôtre.

Avant l'indexation, sur un simple échantillon

POST/v1/rulepacks/suggest

Même comparaison, mais avant tout import : on envoie un échantillon de produits et on obtient le même objet de réponse, sans catalogue indexé. C'est ce qui permet de choisir son pack avant le premier import plutôt qu'après — corriger après coup impose de tout réimporter. Au plus 300 produits sont examinés ; au-delà, le reste du corps est ignoré. Un échantillon vide est refusé par un 422. Ne compte pas dans le quota, et n'écrit rien : aucun catalogue n'est créé.

pack_actuel vaut toujours null dans cette réponse — il n'y a pas encore de catalogue, donc pas de pack en place à comparer.

curl -X POST https://api.heurix.fr/v1/rulepacks/suggest \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"id": "1", "ref": "VIS-M8X20", "name": "Vis tête hexagonale M8x20 inox A2"},
                 {"id": "2", "ref": "ECR-M8", "name": "Écrou hexagonal M8 zingué"}]}'

→ {"classement": [
     {"pack": "outillage", "annotations_distinctes": 9, "produits_annotes": 2, "couverture_pct": 100.0},
     {"pack": "plomberie", "annotations_distinctes": 1, "produits_annotes": 1, "couverture_pct": 50.0}
   ], "recommande": "outillage", "raison": "aucun pack n'est configuré",
   "pack_actuel": null, "echantillon": 2,
   "marge": {"second": "plomberie", "produits_annotes": 2.0,
             "annotations_distinctes": 9.0, "critere": "produits_annotes"}}
GETPOSTDELETE/v1/index/{catalog}/custom-rules

Custom Rules

Étend le pack de règles actif d'un catalogue, sans y toucher — une Custom Rule est propre à un seul catalogue, jamais partagée avec un autre compte. Deux gabarits, sans regex à écrire : keyword (une liste de mots équivalents déclenche tous la même étiquette) et prefix_number (un préfixe suivi d'un nombre devient une étiquette avec la valeur capturée, ex. RAL reconnaît « RAL 9010 » et « RAL9010 »). Effet immédiat : tous les produits déjà indexés sont ré-annotés dès la création de la règle. Jusqu'à 30 règles personnalisées par catalogue.

curl -X POST https://api.heurix.fr/v1/index/moncatalogue/custom-rules \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"rule_type": "keyword", "label": "Cheville",
       "keywords": ["placo", "cheville", "molly", "plaquo"]}'

curl -X POST https://api.heurix.fr/v1/index/moncatalogue/custom-rules \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"rule_type": "prefix_number", "label": "Coloris RAL", "prefix": "RAL"}'

Gérable aussi bien par API que directement depuis la console, dans « Mon catalogue ».

POST/v1/events

Conversion & ROI

Remonte les clics et achats de votre site vers Heurix, pour mesurer le taux de clic sur vos recherches et le chiffre d'affaires — voire la marge — qui en découle. Deux types d'événement : search_click (un clic sur un résultat de recherche) et purchase (un ou plusieurs produits achetés, avec montant et marge optionnelle par produit).

curl -X POST https://api.heurix.fr/v1/events \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"event_type": "search_click", "catalog": "moncatalogue",
       "query": "cheville placo", "product_id": "sku-123"}'

curl -X POST https://api.heurix.fr/v1/events \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{"event_type": "purchase", "catalog": "moncatalogue",
       "products": [{"id": "sku-123", "amount": 29.90, "margin": 8.50}]}'

Cet endpoint est conçu pour être appelé depuis le navigateur du visiteur final, sur votre propre site — pas depuis heurix.fr — d'où un CORS ouvert à toutes origines. Trois façons de le poser, de la plus complète à la plus simple :

  • Heurix Tracker (recommandé) — un identifiant visiteur persistant, pour relier un clic à un achat ultérieur du même visiteur plutôt qu'une simple corrélation agrégée. Un seul script à poser site-wide, expose les mêmes fonctions que le snippet ci-dessous.
  • Modèle de balise Google Tag Manager (fichier .tpl, à importer dans Modèles → Modèles de balises → Importer) — compatible avec le tracker via un champ « Identifiant visiteur » optionnel
  • Snippet JavaScript simple, sans identifiant persistant — pour une intégration minimale sans GTM ni tracker

Ne consomme pas votre quota de requêtes — ce n'est pas un appel moteur, c'est une remontée de données.

À savoir avant de lire vos chiffres : même avec le tracker, l'attribution recherche → achat n'est pas une garantie à 100 %. Sans identifiant visiteur, Heurix agrège les clics et achats reçus sur une même période et un même compte (total_revenue) — une corrélation, pas une preuve. Avec le tracker installé, un champ supplémentaire (attributed_revenue) ne compte que les achats où le même visiteur a cliqué depuis une recherche dans les 24 heures précédentes — un lien réel, nettement plus fiable, mais qui dépend toujours de la qualité de votre implémentation.

GET/v1/analytics/conversion-summary

Taux de clic, chiffre d'affaires et marge attribués sur la période (?days=30 par défaut). Renvoie aussi attributed_revenue et attributed_productsnull si aucun événement de la période ne porte d'identifiant visiteur (le tracker n'est pas installé), un chiffre réel sinon.

GET/v1/analytics/top-products

Produits les plus achetés, triés par volume ou par marge (?sort_by=volume ou margin).

Vues de page de catégorie

Le tracker peut remonter les produits affichés sur une page de catégorie, ce qui permet de repérer ceux qui sont beaucoup montrés et peu cliqués — la population que l'épinglage et la relégation servent justement à corriger.

Heurix.trackCategoryView("visserie", ["sku-1", "sku-2", "sku-3"]);

Un seul appel par page vue, portant la liste des produits affichés — pas un appel par produit. Le moteur enregistre ensuite une impression par produit, ce qui rend l'agrégation possible en une requête. À savoir sur le volume : une page affichant 24 produits génère 24 lignes, purgées automatiquement au-delà de la période de rétention. La liste est plafonnée à 50 identifiants.

GET/v1/analytics/category-views/{catalog}

Renvoie les catégories triées par nombre d'impressions, et dans chacune les produits les plus vus avec leur nombre de clics depuis la recherche. Ne consomme pas votre quota.

{"catalog": "moncatalogue", "categories": [
  {"category": "visserie", "total_views": 512, "products": [
     {"product_id": "sku-1", "views": 180, "search_clicks": 12},
     {"product_id": "sku-2", "views": 175, "search_clicks": 0}
  ]}
]}

Renvoie une liste vide tant que le tracker ne remonte pas cet événement — c'est le cas de toute intégration antérieure à cette fonctionnalité. Rien ne casse, l'écran reste simplement vide.

Produits fréquemment achetés dans le même panier que {product_id} — une co-occurrence comptée, pas du machine learning. Utile pour une section « souvent acheté avec » sur une fiche produit.

{"product_id": "sku-123", "target_purchases": 47, "related": [
  {"product_id": "sku-456", "co_purchases": 12, "name": "Rondelle plate M8 inox", "price": 0.12},
  {"product_id": "sku-789", "co_purchases": 4, "name": "Clé plate 13mm", "price": 4.90}
]}

target_purchases donne le total d'achats du produit demandé sur la période — de quoi calculer un pourcentage plutôt que d'afficher un compte isolé (« 12 sur 47 achats », plus lisible que « 12 »). name et price sont résolus depuis votre catalogue actuel ; null si le produit associé a été retiré du catalogue depuis.

Liste vide si moins de 5 achats sont enregistrés pour ce produit précis — pas assez de signal pour être fiable, jamais une erreur. Nécessite le Heurix Tracker pour un résultat pertinent sur un site à trafic élevé : sans identifiant visiteur, deux achats de clients différents survenus presque au même instant peuvent, rarement, être confondus avec un seul panier.

GET/v1/analytics/intent-score/{catalog}/{visitor_id}

Un score d'intention par visiteur — à distinguer de la popularité sur la recherche, qui est par produit. Explicable par conception : chaque composante est renvoyée séparément, jamais fondue dans un seul nombre qu'on ne pourrait pas justifier.

{"score": 0.72, "nb_recherches": 12, "nb_achats": 2,
 "composantes": {
   "precision_recherche": 0.83,
   "taux_clic": 0.58,
   "panier_moyen": 45.20
 }}

precision_recherche : proportion des recherches de ce visiteur qui portent une référence chiffrée (« M8x20 ») plutôt qu'un mot générique (« vis »). taux_clic : proportion de ces mêmes recherches ayant mené à un clic sur un résultat. score est leur moyenne — les deux seuls signaux qui le composent, sur la même échelle 0 à 1. panier_moyen (en euros, null sans achat) est renvoyé à part, à titre de contexte : aucune échelle commune évidente avec les deux autres signaux, le fondre arbitrairement dans le score aurait été une décision impossible à justifier.

Visiteur jamais vu, ou sans recherche sur la période : score à 0.0, jamais une erreur. Ne consomme pas votre quota.

GET/v1/analytics/segmentation/{catalog}?days=30

Répartit tous les visiteurs actifs d'un catalogue en trois tranches d'intention (même formule que le score d'intention ci-dessus), comparée à la période précédente de même durée.

{"period_days": 30,
 "courant": {"total_visiteurs": 142, "repartition": {"fort": 38, "moyen": 71, "faible": 33}},
 "precedent": {"total_visiteurs": 98, "repartition": {"fort": 19, "moyen": 58, "faible": 21}},
 "variations": {"total_visiteurs": 44.9, "fort": 100.0, "moyen": 22.4, "faible": 57.1}}

variations vaut null plutôt qu'un pourcentage quand la période précédente était vide — passer de 0 à 5 n'est pas une progression de "500 %", c'est un démarrage ; mieux vaut ne rien afficher qu'un chiffre trompeur. Les comptes sont agrégés, jamais une liste nominative : Heurix ne connaît le visiteur que par un identifiant pseudonyme, jamais un nom ni un email. Recalculé au maximum une fois par heure — un signal de comportement agrégé qui n'a pas besoin d'être plus frais que ça.

JSheurix-search.js

Barre de recherche prête à l'emploi

Pas envie de coder l'appel fetch, le rendu des résultats et la gestion des facettes vous-même ? Une bibliothèque JavaScript autonome (zéro dépendance) affiche une barre de recherche complète — saisie, résultats en direct, facettes cliquables, navigation clavier — connectée à votre catalogue en quelques lignes.

Téléchargez heurix-search.js — un seul fichier, à poser où vous voulez sur votre site.

Utilisez une clé publique (hxp_) — ce script tourne dans le navigateur du visiteur, où la clé est lisible par tous. Une clé serveur donnerait accès à votre facturation.

Intégration minimale

<div id="ma-recherche"></div>
<script src="heurix-search.js"></script>
<script>
  Heurix.searchBox({
    apiKey: "VOTRE_CLE_PUBLIQUE",
    catalog: "moncatalogue",
    containerId: "ma-recherche"
  });
</script>

C'est suffisant pour une barre de recherche fonctionnelle — saisie avec anti-rebond intégré, résultats affichés dès 2 caractères, tolérance aux fautes de frappe (gérée côté moteur, rien à faire ici), secours automatique sur zéro résultat si votre catalogue a des produits featured.

Options de configuration

OptionTypeDescription
apiKey, catalog, containerIdstringSeules options obligatoires.
facetsstring[]Champs à proposer en filtres cliquables (ex. ["brand", "color"]). Absent par défaut — aucune facette affichée tant que vous ne les listez pas explicitement.
accentColorstringUne couleur d'accent (ex. "#2952E3") pour le focus et les filtres actifs — personnalisation minimale par design, pas une refonte CSS complète.
placeholderstringTexte du champ de recherche. Défaut : « Rechercher… ».
minCharsnumberNombre de caractères avant de déclencher une recherche. Défaut : 2.
debounceMsnumberDélai d'anti-rebond en millisecondes. Défaut : 200.
limitnumberNombre maximal de résultats affichés. Défaut : 8.
renderItemfunction(hit)Personnalise le HTML de chaque résultat. Reçoit un hit complet (voir la référence de recherche) ; par défaut affiche nom, référence, prix et rupture de stock.
resultHreffunction(hit) → stringSi fourni, chaque résultat devient un lien <a> vers l'URL renvoyée (ex. la fiche produit), plutôt qu'un simple élément cliquable.
seeAllHreffunction(query, total) → stringLe panneau reste volontairement compact (8 résultats par défaut). Si fourni, un lien « Voir les N résultats → » apparaît en pied de liste vers votre page de résultats complets. Sans cette option, le total s'affiche quand même, en texte simple.
onSelectfunction(hit)Appelé au clic ou à la validation clavier (Entrée) sur un résultat — utile si vous gérez la navigation vous-même plutôt que via resultHref.
timeoutMsnumberDélai au-delà duquel l'appel est abandonné, en millisecondes. Défaut : 3000. Un visiteur attend devant sa page : mieux vaut lui rendre la main que le laisser patienter. 0 désactive le délai.
fallbackHreffunction(query) → stringSi Heurix ne répond pas, le panneau propose au visiteur de poursuivre sur l'URL renvoyée — typiquement votre propre page de résultats. Sans cette option, le panneau affiche quand même un bouton « Réessayer » : il ne reste jamais un cul-de-sac.
baseUrlstringDéfaut : https://api.heurix.fr. À ne changer que pour un environnement de test.

Exemple complet, avec facettes et lien produit

Heurix.searchBox({
  apiKey: "hxp_votre_cle_publique",
  catalog: "moncatalogue",
  containerId: "ma-recherche",
  facets: ["brand"],
  accentColor: "#C0392B",
  resultHref: function (hit) {
    return "/produits/" + hit.product.id;
  }
});

Chaque recherche via ce widget consomme le quota normal de la clé utilisée, exactement comme un appel fetch direct au point d'entrée de recherche — ce n'est qu'une façade, pas un nouveau mécanisme de facturation.

Le style visuel fourni reste volontairement minimal — fonctionnel et lisible, pensé pour être facilement personnalisé (chaque élément a une classe CSS préfixée hx-) plutôt que pour correspondre à une charte graphique précise dès cette version.

npm@heurix-site/client

Client officiel TypeScript/JavaScript

Pour un projet Node.js, un build TypeScript, ou simplement préférer un module à un fetch écrit à la main : le client officiel couvre la recherche, Browse & Discovery, l'indexation et les synonymes. Zéro dépendance runtime (utilise fetch natif), types complets.

npm install @heurix-site/client
import { HeurixClient } from "@heurix-site/client";

const client = new HeurixClient({
  apiKey: "hxp_votre_cle_publique",
  catalog: "monsite",
});

const results = await client.search("vis m8 inox");
console.log(results.hits);

Sans étape de build, un import direct depuis un CDN fonctionne aussi bien :

<script type="module">
  import { HeurixClient } from "https://cdn.jsdelivr.net/npm/@heurix-site/client@latest/dist/index.js";
</script>

Même règle que partout ailleurs côté navigateur : une clé publique (hxp_), jamais votre clé serveur, dans du code qui s'exécute chez le visiteur.

Un seul nom à retenir : @heurix-site/client. Le paquet heurix-client (sans organisation) a existé brièvement pendant la mise au point et est aujourd'hui déprécié — s'il apparaît dans un résultat de recherche npm plus ancien, ignorez-le.

MCPServeur Heurix pour agents IA

Interroger Heurix depuis Claude Desktop, Cursor, ou un agent interne

Un serveur MCP (Model Context Protocol) qui expose la recherche et Browse comme des outils qu'un agent IA sait appeler nativement — un collaborateur peut demander « est-ce que j'ai des vis M8 en stock ? » en langage naturel, sans jamais toucher à l'API. Trois outils : heurix_search, heurix_browse, heurix_catalog_stats.

Prérequis : Python 3.10 ou plus, installé sur la machine où tourne l'agent (votre ordinateur, pas un serveur) — le serveur MCP se lance localement, à la demande, par Claude Desktop ou Cursor eux-mêmes.

Téléchargez le serveur MCP — installation et configuration détaillées (Claude Desktop et Cursor) sur le guide dédié.

pip install -r requirements.txt
HEURIX_API_KEY=hx_votre_cle python3 server.py

La clé API vit uniquement dans la configuration locale du client MCP (un fichier JSON sur la machine de l'utilisateur), jamais transmise en clair dans un appel d'outil. Chaque appel consomme le quota normal de la clé utilisée — heurix_search et heurix_browse comptent comme n'importe quel appel de recherche ou de Browse, heurix_catalog_stats non.

GET/health

Santé du service

Liveness, sans authentification — pratique pour vos sondes de supervision.

{"status": "ok", "catalogs": 3}

Glossaire

Le vocabulaire propre à Heurix, dans l'ordre où une requête le traverse : d'abord indexer un catalogue, puis comprendre ce qu'un visiteur tape, puis classer les résultats, enfin suivre et ajuster. Pour les termes plus généraux du secteur (index, facette, ranking...), voir aussi le glossaire du search e-commerce sur le blog.

Indexer un catalogue

Catalogue. Un ensemble de produits indexés sous un même nom, isolé des autres. Un compte peut posséder plusieurs catalogues — une langue, une marque, un environnement de test.

Règle. Un mécanisme de reconnaissance : une expression régulière, une liste de mots-clés, ou un patron préfixe + nombre qui repère un motif dans un texte. Une règle ne modifie rien — elle produit une annotation quand elle reconnaît quelque chose.

Annotation. L'étiquette posée sur un produit quand une règle a reconnu quelque chose dans son texte (référence, nom, description). DIAM_M8, FAM_VIS sont des annotations. Un même produit en accumule plusieurs, une par règle déclenchée.

Pack de règles. Un ensemble de règles prêtes à l'emploi pour un secteur (outillage, mode, électronique...). Choisi à l'indexation, il détermine quelles annotations un catalogue produit automatiquement.

Custom Rule (règle personnalisée). Une règle ajoutée par vous, propre à un seul catalogue, pour un vocabulaire qu'aucun pack ne pouvait deviner. Deux formats : mot-clé → étiquette, ou préfixe + nombre → étiquette.

Comprendre une requête

Tolérance aux fautes de frappe. La capacité à retrouver un produit malgré une faute d'une ou deux lettres. Le nombre de fautes tolérées dépend de la longueur du mot tapé : plus il est court, plus l'écart accepté est faible, pour éviter de confondre deux mots courts sans rapport.

Distance d'édition. La mesure derrière la tolérance aux fautes : le nombre minimal d'ajouts, de suppressions ou de substitutions de lettres pour transformer un mot tapé en un mot connu. « tshi » → « tshirt » : distance 2.

Correspondance floue (fuzzy matching). Le processus qui accepte un mot tapé comme correspondance d'un mot du catalogue tant que leur distance d'édition reste dans la tolérance autorisée.

Préfixe. Une requête encore en train d'être tapée. Contrairement à la tolérance aux fautes, qui suppose un mot complet et possiblement mal orthographié, la recherche par préfixe reconnaît qu'un mot incomplet mène sans ambiguïté vers un mot plus long du catalogue.

Synonyme. Une déclaration d'équivalence entre plusieurs mots libres (« vis » = « boulon » = « screw »). Contrairement à une annotation, un synonyme n'étiquette rien sur le produit : il élargit ce qu'une requête peut atteindre, au moment de la recherche.

Facette. Un filtre construit automatiquement à partir d'un champ du catalogue (marque, couleur, matière), pour affiner une liste de résultats sans retaper de requête.

Classer les résultats

Pertinence (score). La note attribuée à chaque résultat pour déterminer son rang. Elle combine la nature de la correspondance — exacte, floue, par préfixe, par synonyme — et son poids : un mot trouvé exactement pèse toujours plus qu'un mot retrouvé après tolérance aux fautes.

Ranking (classement). L'ordre final dans lequel les résultats sont présentés, une fois la pertinence calculée et les règles de priorité appliquées.

Priorité de requête (Search Override). Une règle manuelle qui épingle ou relègue un produit précis pour une requête précise (« sur "promo", toujours montrer X en premier »). Indépendante du classement naturel.

Browse & Discovery. Le classement des produits sur une page de catégorie, sans requête de recherche associée. Fonctionne avec ses propres priorités et boosts, indépendamment de la recherche.

Boost / relégation par attribut. Une règle qui met en avant ou recule tous les produits partageant un attribut commun (une marque, une couleur), sans les cibler un par un. Une relégation individuelle garde toujours la priorité sur un boost par attribut.

Regroupement par famille. Le rassemblement de résultats très proches (les mêmes vis en douze longueurs) sous une entrée représentative, pour qu'une requête large reste lisible plutôt que noyée sous des variantes.

Suivre et ajuster

Requête à zéro résultat. Une recherche qui n'a rien trouvé. Chaque occurrence est un signal direct : soit un synonyme manque, soit le produit cherché n'existe pas au catalogue.

Champ matched. La liste, incluse dans chaque résultat, des termes et annotations qui l'ont fait matcher. C'est l'outil de diagnostic : si un résultat surprend, ce champ montre pourquoi.

Clé API (serveur / publique). La clé serveur autorise tout, y compris l'indexation — elle ne doit jamais apparaître dans une page web. La clé publique ne peut que chercher et consulter le catalogue : c'est la seule à poser dans le code d'un site.

Bac à sable (sandbox). Un catalogue marqué comme test : il n'est pas facturé et ses recherches n'apparaissent pas dans les statistiques. Utile pour essayer une configuration sans toucher à de vraies données.

La cascade d'annotations

C'est le cœur du moteur. Un pack de règles est organisé en niveaux : le niveau 1 applique des expressions régulières sur le texte normalisé et produit des annotations ; les niveaux suivants s'appliquent sur le flux d'annotations et les composent. Documents et requêtes traversent la même cascade, la correspondance se fait donc dans un espace partagé :

« m8x20 inox »                          « M8 x 20 — A2 »   (fiche produit)
      │                                        │
      ▼  niveau 1                              ▼  niveau 1
DIAM_M8, LONG_20, MAT_INOX             DIAM_M8, LONG_20, MAT_INOX
      │                                        │
      ▼  niveau 2                              ▼  niveau 2
VIS_M8X20  ────────── correspondance ──────  VIS_M8X20

Chaque annotation partagée pèse fortement dans le score — c'est ce qui fait passer la bonne référence en tête même quand la forme écrite diffère. Le champ matched de chaque résultat rend ces annotations visibles.

Bonnes pratiques

Remplissez ref, name et description distinctement. C'est la pondération entre ces trois champs qui fait la qualité du tri — tout concaténer dans name aplatit la pertinence.

Gardez les références telles quelles. N'essayez pas de « nettoyer » vos références produits avant indexation (retirer les espaces, les tirets...) : la normalisation et la cascade s'en chargent, et la forme d'origine reste disponible pour l'affichage.

Indexez par lots de 1 000 à 5 000. Un lot = un appel réseau ; trop petit multiplie les appels, la limite est à 5 000 par appel.

Envoyez le stock à chaque mise à jour. Le tri à pertinence égale repose dessus ; un stock à jour évite de mettre en avant des produits indisponibles.

Un catalogue par langue. Si votre boutique existe en français et en anglais, créez boutique-fr et boutique-en — les synonymes et les règles restent cohérents par langue.

Utilisez matched pour régler la pertinence. Chaque résultat explique pourquoi il sort : si un produit remonte à tort, ce champ montre quel terme ou quelle annotation l'a fait matcher — c'est votre outil de diagnostic.

Codes d'erreur

CodeSignificationQue faire
401En-tête Authorization absent ou mal forméEnvoyez Authorization: Bearer <clé>
403Clé API invalideVérifiez la clé ; contactez-nous si elle a été révoquée
404Catalogue ou produit introuvableVérifiez le nom du catalogue et l'id — le catalogue est créé au premier appel d'indexation, pas avant
422Corps de requête invalideLe détail de l'erreur indique le champ en cause (ex : produit sans id)
5xxErreur côté serveurRéessayez ; si l'erreur persiste, écrivez à contact@heurix.fr

Les messages d'erreur sont en français. Le code HTTP et la structure de la réponse sont le contrat stable et documenté — branchez-vous dessus, pas sur le texte de detail, qui est une phrase écrite pour la personne qui lit la réponse brute et peut être reformulée. Sur un 422, detail est la liste des erreurs de validation : loc nomme le champ en cause et msg porte le message du validateur, en anglais pour tout ce que le schéma lui-même refuse.

Limites

Requête de recherche500 caractères maximum
Résultats par page100 maximum (limit)
Indexation5 000 produits par appel — envoyez plusieurs lots pour un catalogue plus grand
Groupes de synonymes2 000 par catalogue
CORSOuverts aux clés publiques (hxp_) sur les endpoints de recherche, de parcours et de conversion — c'est le mode d'intégration prévu pour le widget. Restreignez-les à vos domaines avec allowed_origins. Les clés serveur (hx_) ne doivent jamais partir d'un navigateur : elles donnent accès à l'indexation et à la facturation.

Un besoin au-delà de ces limites ? Parlons-en — c'est le rôle du plan Scale et des configurations dédiées.

Essai gratuit 14 jours

Testez Heurix sur votre catalogue, sans carte bancaire.

Voir les tarifs