Documentation — niveau 3 sur 3

Référence de l'API publique

111 opérations, engendrées depuis la spécification OpenAPI 3.1 du Hub. Cette page ne peut pas diverger du code : elle est fabriquée à partir du même document que celui que sert « GET /api/v1/specification », à chaque affichage.

Base : https://www.structureweb.fr — version du Hub : 4.58 — version du contrat : 2026-10-16.02.

Conventions communes

  • Authentification : « Authorization: Bearer <clé> » sur toutes les routes à clé. La clé identifie la boutique — aucun identifiant à passer.
  • Les montants sont TOUJOURS en centimes, en entiers. Les dates sont en ISO 8601.
  • Les paramètres de requête sont ceux qui sont listés, et eux seuls : tout autre paramètre est REFUSÉ en 400, jamais ignoré en silence.
  • Les erreurs ont une forme unique : { "error": "…" }, parfois avec « details » (champ → raisons).
  • « message » s'adresse à l'intégrateur ; « messagePourAcheteur » est prêt à afficher à un consommateur. N'affiche jamais « message » ni « error » à un acheteur.
  • ⛔ Un appel en « http:// » est REFUSÉ (426), jamais redirigé : une redirection retirerait l'en-tête « Authorization », et la clé aurait déjà traversé le réseau EN CLAIR — considérez-la alors comme COMPROMISE.

Les codes HTTP rendus par l'API

CodeCe qu'il veut dire
200Toutes les lignes écrites.
201Message enregistré.
202Visites enregistrées.
207Au moins une ligne a échoué (« failed » > 0). Même forme ; les lignes réussies sont écrites.
400Corps non JSON.
401Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
402Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
403Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
404« Client introuvable. »
405Toujours : méthode refusée. Le corps dit quel verbe employer.
409Capacité « fidelite » fermée.
413« corps_trop_grand » : au-delà de 64 Kio.
422Valeur de « status » hors liste (code « invalid_filter_value »).
429Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
500Erreur non interceptée par la route (exception de la base remontée telle quelle) — corps non établi.
502carrier_unavailable : le transporteur n'a pas répondu à la recherche (ne veut PAS dire « aucun relais »).
503Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).

Les codes d'erreur nommés

47 valeurs que le champ « error » ou « code » peut prendre. Écris tes règles en liste blanche sur ces valeurs : une règle qui conclut « ce n'est pas tel refus, donc c'est un succès » prendra pour un succès toute valeur ajoutée plus tard.

  • AUCUNE_TRANCHE
  • BORNES_INVERSEES
  • brand_in_use
  • card_payment_not_confirmed
  • carrier_key_unreadable
  • carrier_no_service_points
  • carrier_not_connected
  • carrier_unavailable
  • carrier_unknown
  • category_in_use
  • CHEVAUCHEMENT
  • country_forbidden
  • country_not_served
  • country_unreadable
  • delivery_code_inactive
  • deploy_hook_unreachable
  • discount_already_used
  • invalid_country
  • invalid_filter_value
  • invalid_postal_code
  • invalid_status
  • method_not_allowed
  • missing_request_id
  • missing_version
  • NE_COMMENCE_PAS_A_ZERO
  • organization_not_found
  • organization_rebuilding
  • paid_at_dans_le_futur
  • paid_at_trop_ancien
  • pickup_slot_invalid
  • PLUSIEURS_INFINIS
  • POIDS_NEGATIF
  • postal_code_refused
  • postal_code_required
  • PRIX_NEGATIF
  • PRIX_NON_ENTIER
  • product_delivery_mode_refused
  • produit_disponible
  • produit_introuvable
  • service_point_not_found
  • stale_request_id
  • subscription_required
  • too_many_attempts
  • trop_d_inscriptions
  • TROU_DANS_LA_GRILLE
  • variante_introuvable
  • version_too_long

Toutes les routes

111 opérations, réparties en 35 rubriques. Aucune n'est omise : une route absente d'une documentation n'existe pas pour qui la lit.

Arrêt sanitaire

GET /api/v1/arrets-sanitaires

Les arrêts de vente EN COURS (jamais les arrêts levés).

« data » est TOUJOURS présent ; « [] » veut dire « rien n'est coupé aujourd'hui », et c'est une information. Chaque entrée porte « portee » (« BOUTIQUE », « CATEGORIE », « PRODUITS »), « motif » (à AFFICHER à l'acheteur), « categoryId », « productIds » et « depuis ». ⭐ Le même état est rendu par « GET /api/v1/webhook » sous « arretsSanitaires », et chaque produit porte « arretSanitaire » dans « GET /api/v1/products » — cette dernière forme est la plus sûre : elle ne peut pas se tromper de périmètre.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — « data » : les arrêts en cours.
  • 401 — Clé d'API absente ou invalide. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, contrat 2026-09-16.30.

POST /api/v1/arrets-sanitaires

Coupe la vente d'articles (portée BOUTIQUE, CATEGORIE ou PRODUITS), ou lève un arrêt en cours.

⭐ DEUX CHEMINS, TOUJOURS LES DEUX : le commerçant le fait depuis son Hub, OU son agent le fait pour lui par ici. ⛔ N'ÉTEINS JAMAIS L'ÉCRAN DU HUB sous prétexte que tu sais le faire : le jour où tu n'es pas joignable, il doit pouvoir couper lui-même. COUPER : { « portee », « motif », et « categoryId » ou « productIds » }. LEVER : { « lever »: « <id> », « motif » }. ⚠️ « motif » est obligatoire dans les DEUX sens : il s'affiche à l'acheteur, et il reste comme trace — reprendre une vente suspendue se justifie devant un contrôle autant que l'avoir suspendue. ⚠️⚠️ CE QUE CETTE ROUTE DÉCLENCHE : l'évènement « sanitaire.arret » (ou « sanitaire.reprise ») part IMMÉDIATEMENT sur l'adresse de retour du site. Il n'y a rien d'autre à prévenir. ⛔ ET LA RÈGLE QUE LE SITE DOIT TENIR : un arrêt ne refuse JAMAIS un panier entier — seules les lignes visées tombent, le reste se commande.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Arrêt levé : « leve », « id ».
  • 201 — Arrêt créé : « id », « portee », « pousseAuSite », « rappel ».
  • 400 — « json_invalide », « portee_inconnue », « motif_requis », « categorie_requise » ou « produits_requis ».
  • 401 — Clé d'API absente ou invalide. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « arret_introuvable » : aucun arrêt EN COURS ne porte cet identifiant dans cette boutique.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, contrat 2026-09-16.30.

Articles

DELETE /api/v1/articles/{identifier}

Supprime DÉFINITIVEMENT un article ; avertit si l'article était en ligne.

Supprimer n'est pas dépublier : pour masquer, renvoyer l'article avec « status »: « DRAFT ».

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — Article désignée par son « id » Hub, son « externalRef » OU son « slug » (le premier enregistrement trouvé de l'organisation parmi les trois).
Codes de réponse
  • 200 — Article supprimé.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Article introuvable. »
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).

GET /api/v1/articles

Liste les articles de blog (PUBLIÉS par défaut), pagination par curseur, filtre delta « since ».

Tri par « updatedAt » décroissant. Page de 100 par défaut (« limit » 1 à 200). « readingMinutes » est calculé à partir du contenu (200 mots/minute, minimum 1). « emptyReason » tient compte de « since » mais PAS de « status ». L'appel à « unknownParameterWarnings » ne peut rien produire : tout paramètre inconnu est refusé en 400 par « parseListParams ».

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • since (query) — Articles dont « updatedAt » ≥ cette date. Date illisible → 400.
  • limit (query) — Hors bornes ou non entier → 400.
  • cursor (query) — « nextCursor » de la page précédente.
  • status (query) — Absent = PUBLISHED. « ALL » = tous statuts (y compris SCHEDULED, qui n'est PAS accepté comme valeur de filtre : 422). Casse ignorée.
Codes de réponse
  • 200 — Enveloppe de liste augmentée de « blog ».
  • 400 — Paramètre inconnu (le message liste « since », « limit », « cursor », « status ») ; « since » illisible ; « limit » hors 1..200.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Valeur de « status » hors liste (code « invalid_filter_value »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.4.
Réserves
  • Non établi depuis le code : « cursor » désignant un id inexistant ou hors de l'organisation : réaction de Prisma non établie depuis le code.

GET /api/v1/articles/{identifier}

Rend un article, quel que soit son statut.

Pas de « readingMinutes » ici (seule la liste le calcule).

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — Article désignée par son « id » Hub, son « externalRef » OU son « slug » (le premier enregistrement trouvé de l'organisation parmi les trois).
Codes de réponse
  • 200 — L'article.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Article introuvable. »
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).

POST /api/v1/articles

Synchronise un lot d'articles de blog (site → Hub), idempotent par « externalRef ».

Corps : tableau brut ou { "articles": [...] }, 1 à 1000. CRÉATION : « title », « slug » et un contenu sont requis (sinon la ligne échoue dans « errors ») ; statut par défaut PUBLISHED, « publishedAt » par défaut maintenant si publié. MISE À JOUR : fusion — un champ absent n'est pas touché (statut et date compris). Un article du Hub sans référence et portant le même slug est ADOPTÉ (compté « updated »). Contenu au choix, priorité « contentHtml » → « content » (chaîne ou tableau de blocs) → « blocks » ; nettoyé en liste blanche. « category », « keywords », « readingMinutes » sont validés mais NON stockés. Une clé inconnue est ignorée en silence (pas de « ignoredFields »). La synchronisation ne supprime rien. Paramètres d'URL autres que « dryRun » : non contrôlés.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON disponible sur « POST /api/v1/articles » : sa simple présence, quelle que soit sa valeur (même vide), est refusée en 400 — AVANT le contrôle de la clé. L'essai à blanc n'existe que sur « POST /api/v1/products ».
  • deploy (query) — « 1 » (exactement) : demande la reconstruction du site après l'écriture, si aucune ligne n'a échoué. Toute autre valeur est sans effet et n'ajoute aucune clé « deploy » à la réponse.
Codes de réponse
  • 200 — Aucune ligne en échec.
  • 207 — Au moins une ligne a échoué (« failed » > 0). Même forme ; les lignes réussies sont écrites.
  • 400 — « dryRun » présent dans l'URL (contrôlé AVANT la clé), ou corps qui n'est pas du JSON valide.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Corps invalide (« Les données d'articles sont invalides. »). « details.articles » est REMPLACÉ par une liste de messages précis : « article #N (référence) → champ : message (actuellement X caractères) ».
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.4.

Audience

POST /api/v1/analytics/visits

Enregistre une visite de page, ou un lot de 1 à 500 visites, remontées par le site.

Deux formes : une visite seule, ou { "visits": [ … ] }. ⚠️ Un corps qui porte à la fois « path »/« visitorHash » ET « visits » est lu comme UNE visite seule (« visits » ignoré). Champs non décrits ignorés. Alimente l'écran « Statistiques » du gérant. ⚠️ « ?dryRun » n'est PAS refusé ici : il est ignoré et les visites sont enregistrées.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 202 — Visites enregistrées.
  • 400 — Corps qui n'est pas du JSON valide (« Le corps de la requête doit être un JSON valide. »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « Les données de visite sont invalides. » ; « details » = erreurs par champ (« path », « visitorHash », « occurredAt », ou « visits » pour un lot). Peut être {} quand le corps ne correspond à aucune des deux formes (ex. « path » absent).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.11.

Avis produits

GET /api/v1/reviews

Liste les avis produits PUBLIÉS (au plus 200, du plus récent), avec note moyenne et mention légale.

Les paramètres d'URL sont contrôlés AVANT la clé (un paramètre inconnu rend 400 même sans clé). Les avis REFUSED ne sont jamais rendus. ⚠️ « averageRating », « ratingCount » et « ratingBreakdown » sont calculés sur les avis RENDUS par cette requête (donc au plus 200, et selon status/produit), en ne comptant que les PUBLISHED dont la mention « vérifié » tient : avec « status=PENDING » ils valent null / 0. Pas d'enveloppe listResponse : ni « warning » ni « emptyReason », « nextCursor » toujours null.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • productExternalRef (query) — Égalité exacte avec la référence produit portée par l'avis.
  • productId (query) — Égalité exacte avec l'id Hub du produit.
  • status (query) — Sensible à la casse. « ALL » = PUBLISHED + PENDING. Autre valeur : 422 « invalid_status ».
  • limit (query) — Accepté (refuserFiltresInconnus pagine par défaut) mais SANS EFFET : la route rend toujours au plus 200 avis.
  • cursor (query) — Accepté mais SANS EFFET : aucune pagination.
Codes de réponse
  • 200 — Avis et agrégats.
  • 400 — Paramètre d'URL inconnu (« Filtre inconnu : … »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Valeur de « status » non acceptée.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 6.3 nonies.

POST /api/v1/reviews

Transmet un avis déposé par un acheteur ; refusé en 422 si aucun achat payé n'est prouvé (sauf réglage « ALL »).

Corps lu SANS schéma Zod : un champ texte qui n'est pas une chaîne est traité comme absent. Le produit est résolu par « productId » (id Hub) ou « productExternalRef » (externalRef ou slug du produit) ; un produit introuvable n'est pas refusé en soi. Vérification d'achat : commande de la même adresse (insensible à la casse), au statut PAID/PROCESSING/SHIPPED/DELIVERED, contenant ce produit, pas déjà utilisée par un autre avis. Réglage « reviewsWhoCanPost » ≠ « ALL » (défaut) et achat non prouvé → 422, SAUF rejeu d'un « externalRef » déjà en base. Avec « externalRef » : upsert qui ne modifie JAMAIS un avis existant (« created »: false, l'avis d'origine est rendu). Un avis nouveau est PENDING ; le gérant est prévenu par e-mail (best-effort). « ?dryRun » est refusé en 400 avant la vérification de la clé.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON DISPONIBLE sur « POST /api/v1/reviews » : sa seule présence (quelle que soit la valeur) est refusée en 400 par refuserDryRunNonSupporte, AVANT la vérification de la clé. Ne l'envoie pas.
Codes de réponse
  • 201 — Avis enregistré (« created »: true) ou rejeu d'une référence existante (« created »: false, rien d'enregistré). ⚠⚠ « message » s'adresse à toi, l'intégrateur ; « messagePourAcheteur » est prêt à afficher tel quel à un consommateur. N'affiche JAMAIS « message » ni « error » à un acheteur.
  • 400 — Corps non JSON, ou « ?dryRun » présent.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Quatre cas : corps non objet (« Le corps de la requête doit être un objet. », sans details) ; saisie invalide (details = { <MOTIF>: [message] } avec MOTIF ∈ AUTEUR_VIDE, NOTE_ILLISIBLE, NOTE_HORS_BAREME, COMMENTAIRE_VIDE, COMMENTAIRE_TROP_LONG) ; aucun produit indiqué (sans details) ; achat non prouvé — ⚠️ là « details » est la CHAÎNE "review_purchase_not_found", pas un objet, ET c'est le SEUL des quatre cas qui porte « messagePourAcheteur ». ⚠⚠ « message » s'adresse à toi, l'intégrateur ; « messagePourAcheteur » est prêt à afficher tel quel à un consommateur. N'affiche JAMAIS « message » ni « error » à un acheteur. Ici « error » cite l'article L.111-7-2 du code de la consommation et les avis vérifiés de Google : montré à un acheteur, il n'a aucun sens pour lui.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 6.3 nonies.

Cache du site

POST /api/v1/cache/purged

Le site confirme (ou infirme) la purge de cache demandée par l'évènement « cache.purge_requested ».

N'accepte que le « requestId » de la demande EN COURS ; il est effacé après usage (succès comme échec), donc un rejeu rend 409. « purged » vaut false UNIQUEMENT si le corps porte le booléen false ; toute autre valeur ou absence vaut true. Corps illisible = traité comme vide (422 missing_request_id). Paramètres d'URL : ignorés (« ?dryRun » n'est pas contrôlé ici).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Confirmation enregistrée.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 409 — « requestId » différent de la demande en cours (périmé, remplacé, déjà confirmé, ou aucune demande en cours) : { error: "stale_request_id", message }.
  • 422 — « requestId » absent, vide ou non-chaîne : { error: "missing_request_id", message }.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.26.

Canal agent-à-agent

GET /api/v1/agent-messages

Lit le fil du canal agent-à-agent de l'organisation : état du canal, compteurs, tour de parole et messages (troncature signalée).

N'est JAMAIS refusé parce que le canal est fermé : l'état est rendu dans « canal » et « note ». ⚠️ Effet de bord : chaque lecture pose la date de relève du fil (« canalAgentVuAt ») — c'est elle qui sert à calculer « nonLus » et « recu » à la lecture SUIVANTE (le calcul se fait contre la date d'avant cette lecture). Le Hub charge au plus les 500 PREMIERS messages du fil (tri par date croissante) ; tous les compteurs, « data », « tonTour » et « tourDeParole » sont calculés sur ces 500 messages ; « total » compte le fil entier en base. Sélection par défaut (sans « before ») : tout message non traité, les 20 derniers, et tout message créé depuis l'ouverture de la session en cours. Avec « before=<id> » d'un message connu : TOUS les messages chargés antérieurs à celui-ci (pas une page). Un « before » inconnu est ignoré en silence (sélection par défaut rendue).

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • before (query) — Curseur pour remonter le fil : identifiant d'un message (celui rendu dans « nextBefore »). Rend tous les messages chargés strictement plus anciens que lui. Identifiant inconnu : ignoré (sélection par défaut).
  • limit (query) — Accepté d'office par « refuserFiltresInconnus » mais JAMAIS lu par cette route : aucun effet sur la réponse.
  • cursor (query) — Accepté d'office par « refuserFiltresInconnus » mais JAMAIS lu par cette route : aucun effet (utiliser « before »).
Codes de réponse
  • 200 — État du canal et fil de messages.
  • 400 — Paramètre d'URL inconnu (tout autre que « before », « limit », « cursor ») ; le message liste ceux acceptés.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 6.9.

GET /api/v1/agent-messages/pieces/{id}

Télécharge les octets d'une pièce jointe du canal (fichier binaire, nom d'origine).

Pièce cherchée par identifiant ET rattachement du message à l'organisation de la clé. Réponse NON JSON : le corps est le fichier. Paramètres d'URL : ignorés.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • id (path, obligatoire) — Identifiant de la pièce (champ « pieces[].id » de GET /api/v1/agent-messages).
Codes de réponse
  • 200 — Octets de la pièce. Content-Type = type MIME enregistré (un de : image/png, image/jpeg, image/webp, image/gif, application/pdf, text/plain, text/csv, text/markdown).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Pièce inexistante OU appartenant à une autre organisation (même réponse) : « Cette pièce jointe n'existe pas, ou n'est pas la tienne. »
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Réserves
  • Non établi depuis le code : Le processus qui efface les pièces après « purgeAfter » (30 jours) n'a pas été lu : la route elle-même ne contrôle pas « purgeAfter » ; une pièce effacée rend 404.

POST /api/v1/agent-messages

Écrit un message à l'agent de l'éditeur (un seul tour à la fois ; pièces jointes facultatives).

Ordre des contrôles : « ?dryRun » (400) → clé d'écriture → JSON (400) → schéma (422) → canal ouvert (403) → l'éditeur a-t-il ouvert la conversation depuis l'ouverture de la session (409) → tour de parole (409, sauf « forcerTour: true ») → pièces jointes (422) → création (201). Avec « forcerTour: true », le corps enregistré est préfixé par « ⚠️ TOUR FORCÉ — <motifTour> » puis une ligne vide (« aucun motif donné » si « motifTour » est absent). Effets de bord : la date de relève du fil est posée et l'échéance de session repoussée de 4 h (si une session court encore) — dès que le tour de parole est accepté, AVANT le contrôle des pièces jointes. Paramètres d'URL autres que « dryRun » : ignorés.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON DISPONIBLE sur cette route : sa seule présence (quelle que soit sa valeur) est refusée en 400 par « refuserDryRunNonSupporte », avant tout traitement. Ne pas l'envoyer.
Codes de réponse
  • 201 — Message enregistré.
  • 400 — « ?dryRun » présent dans l'URL, OU corps qui n'est pas un JSON valide.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Canal fermé (motif propre à la route) : soit ÉTEINT pour cette boutique (le gérant l'allume dans son Hub), soit allumé sans SESSION de travail ouverte. Le message « error » dit lequel. — OU, commun à toutes les routes à clé : L'organisation associée n'est pas active, OU la clé est en LECTURE SEULE et cette opération écrit (le message dit le geste : une clé avec droits d'écriture, « Réglages → API »).
  • 409 — Deux motifs : (1) la session est ouverte mais l'éditeur n'a pas encore écrit depuis son ouverture — c'est lui qui ouvre la conversation ; (2) le dernier message du fil vient du site et n'est pas traité, et « forcerTour » ne vaut pas true.
  • 422 — Deux cas : (a) schéma invalide — error « Message invalide. », « details » = erreurs par champ (« corps », « pieces », « motifTour »…) ; (b) une pièce jointe refusée à la vérification — error « Pièce jointe refusée (« <nomFichier> ») : <motif> », SANS « details ». Rien n'est créé dans les deux cas.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 6.9.

POST /api/v1/agent-messages/traite

Marque traités (ou défait le marquage de) des messages de l'ÉDITEUR, avec essai à blanc possible.

Ne touche que les messages de sens éditeur → site de l'organisation. Marquer : ne vise que les non traités, pose « traiteAt » (et « resume » s'il est fourni) et pose l'accusé de lecture là où il manquait. Défaire (« defaire: true ») : ne vise que les traités et remet « traiteAt » à null. Un identifiant inconnu ou non concerné est simplement non compté (pas de 404). Avec « tous: true », « ids » est ignoré. ⚠️ Aucun contrôle des paramètres d'URL — « ?dryRun » N'EST PAS refusé ici et n'a aucun effet : l'essai à blanc est le champ « simulation » du corps.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Deux formes. Appel réel : object, geste, marques, defaits, ids, restants, avertissement?, note. Essai à blanc (« simulation: true ») : object, simule (true), geste, marquerait, defairait, ids, restants, avertissement?, note (« ESSAI À BLANC — rien n'a été écrit. Retirez « simulation » pour agir. »).
  • 400 — Corps illisible : « Le corps de la requête doit être un JSON valide. »
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Corps invalide — error « Demande invalide. » ; « details » nomme les champs fautifs, et « _demande » porte les règles entre champs (ni ids ni tous ; defaire avec tous).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).

POST /api/v1/agent-messages/vu

Accuse réception (lecture) des messages de l'ÉDITEUR ; n'en marque aucun comme traité. Essai à blanc possible.

Ne touche que les messages éditeur → site de l'organisation sans accusé de lecture (« vuAt » nul). Corps vide ou absent accepté : équivaut à {} et vise TOUS les messages non encore vus. N'influence pas le tour de parole. ⚠️ Aucun contrôle des paramètres d'URL — « ?dryRun » n'est pas refusé et n'a aucun effet : l'essai à blanc est le champ « simulation ».

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Deux formes. Appel réel : object, marques, ids, aRepondre, note. Essai à blanc : object, simule (true), marquerait, ids, note (« ESSAI À BLANC — rien n'a été écrit. Retirez « simulation » pour agir. »).
  • 400 — Corps illisible : « Le corps de la requête doit être un JSON valide. »
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Corps invalide — error « Demande invalide. », « details » = erreurs par champ (ids, simulation). ⚠️ Un champ INCONNU (schéma strict) est refusé en 422 avec « details » VIDE ({}) : l'erreur de niveau racine n'est pas rendue par cette route.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 5.

Catégories

DELETE /api/v1/categories/{identifier}

Supprime DÉFINITIVEMENT une catégorie vide ; refuse en 409 si elle porte des produits ou des sous-catégories.

Pour seulement la retirer de la boutique, envoyer « isActive »: false par POST /api/v1/categories.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — Catégorie désignée par son « id » Hub, son « externalRef » OU son « slug » (le premier enregistrement trouvé de l'organisation parmi les trois).
Codes de réponse
  • 200 — Catégorie supprimée.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Rubrique introuvable. »
  • 409 — Des produits ou des sous-catégories y sont encore rattachés. Forme PROPRE (pas « Erreur ») : « error » est un code.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).

GET /api/v1/categories

Liste TOUTES les catégories (actives par défaut) ; ne pagine pas.

Tri : « position » croissante puis « name ». Toujours la liste complète : « limit » et « cursor » sont ACCEPTÉS mais SANS EFFET, et la réponse le signale (« warningPagination »). Le champ « position » n'est pas rendu dans la liste (il l'est par GET /api/v1/categories/{identifier}). L'appel à « unknownParameterWarnings » ne peut rien produire : tout autre paramètre est refusé en 400.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • isActive (query) — Visibilité. Absent = « true » (seulement ce qui est en ligne). « false » = seulement ce qui est masqué, « all » = tout. Casse et espaces de bord ignorés. Toute autre valeur → 422 « invalid_filter_value ».
  • limit (query) — Accepté, SANS EFFET (aucune validation de la valeur) ; ajoute « warningPagination » à la réponse.
  • cursor (query) — Accepté, SANS EFFET ; ajoute « warningPagination » à la réponse.
Codes de réponse
  • 200 — Enveloppe de liste, « nextCursor » toujours null.
  • 400 — Paramètre autre que « isActive », « limit », « cursor ».
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Valeur de « isActive » autre que true / false / all (code « invalid_filter_value »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.1.
Réserves
  • Volontairement non détaillé : « emptyReason » est calculé SANS tenir compte du filtre « isActive » : « ?isActive=false » sans résultat rend « empty_unexpected » dès que l'organisation a des catégories.

GET /api/v1/categories/{identifier}

Rend une catégorie (active ou masquée) avec ses compteurs de produits et de sous-catégories.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — Catégorie désignée par son « id » Hub, son « externalRef » OU son « slug » (le premier enregistrement trouvé de l'organisation parmi les trois).
Codes de réponse
  • 200 — La catégorie.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Rubrique introuvable. »
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).

POST /api/v1/categories

Synchronise un lot de catégories (site → Hub), idempotent par « externalRef » puis par « slug ».

Corps : tableau brut ou { "categories": [...] }, 1 à 1000. Une catégorie existante est retrouvée par « externalRef », à défaut par « slug » — elle est alors mise à jour en place ET son « externalRef » est aligné sur celui envoyé. Mise à jour = fusion : « name » et « slug » sont écrits, les autres champs seulement s'ils sont présents. Arborescence en seconde passe : « parentExternalRef » n'est résolu que parmi les catégories DE CE LOT ; un parent introuvable ou auto-référent ajoute une entrée dans « errors » SANS incrémenter « failed » (réponse 200 possible avec « errors » non vide). « parentExternalRef »: null ne détache pas. La synchronisation ne supprime rien. Paramètres d'URL autres que « dryRun » : non contrôlés (ignorés).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON disponible sur « POST /api/v1/categories » : sa simple présence, quelle que soit sa valeur (même vide), est refusée en 400 — AVANT le contrôle de la clé. L'essai à blanc n'existe que sur « POST /api/v1/products ».
  • deploy (query) — « 1 » (exactement) : demande la reconstruction du site après l'écriture, si aucune ligne n'a échoué. Toute autre valeur est sans effet et n'ajoute aucune clé « deploy » à la réponse.
Codes de réponse
  • 200 — Aucune ligne en échec (« errors » peut malgré tout porter des liens hiérarchiques ignorés).
  • 207 — Au moins une ligne a échoué en première passe (« failed » > 0). Même forme ; les lignes réussies sont écrites.
  • 400 — « dryRun » présent dans l'URL (contrôlé AVANT la clé), ou corps qui n'est pas du JSON valide.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Corps invalide (« Les données de catégories sont invalides. ») ; « details » = « flatten().fieldErrors », clés de premier niveau (en pratique « categories »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.1.

Clients

DELETE /api/v1/customers/{identifier}

Supprime définitivement un client (nettoyage / RGPD).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — E-mail URL-encodé ou id Hub.
Codes de réponse
  • 200 — Supprimé.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Client introuvable. »
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.8.

GET /api/v1/customers

Liste les clients (contacts) de l'organisation (enveloppe de liste, pagination par curseur).

Tri par date de création décroissante. « since » porte sur updatedAt. « email » : égalité INSENSIBLE à la casse, espaces retirés ; vide = ignoré. ⚠️ Seul « since » compte comme filtre pour l'indicateur de vide : une recherche « email » sans résultat, dans une organisation qui a des clients, rend « emptyReason: empty_unexpected ».

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • since (query) — Filtre delta : ne rend que les éléments dont la date de référence est postérieure ou égale. Toute valeur que `new Date()` ne sait pas lire est refusée en 400 « Le filtre « since » doit être une date ISO valide. ».
  • limit (query) — Taille de page. Hors de 1..200 ou non entier : 400.
  • cursor (query) — Curseur de pagination : l'« id » du dernier élément de la page précédente (valeur de « nextCursor »).
  • email (query) — Adresse exacte recherchée (insensible à la casse).
Codes de réponse
  • 200 — Liste des clients.
  • 400 — Paramètre inconnu (« Filtre inconnu : … »), « since » illisible ou « limit » hors 1..200.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.8.

GET /api/v1/customers/{identifier}

Consulte un client par son e-mail (URL-encodé) ou son identifiant Hub.

Le segment est décodé (decodeURIComponent) puis comparé à l'e-mail (insensible à la casse) OU à l'id.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — E-mail URL-encodé (ex. acheteur%40exemple.fr) ou id Hub.
Codes de réponse
  • 200 — Le client.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Client introuvable. »
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.8.
Réserves
  • Non établi depuis le code : Réponse à un segment contenant un « % » mal formé (decodeURIComponent lève une exception, non interceptée par la route).

GET /api/v1/customers/{identifier}/loyalty

La fidélité d'un acheteur : soldes de points, carte à tampons, palier, code de parrain, bons disponibles.

⭐ 16/10/2026 (contrat 2026-10-16.02, capacité « fidelite ») — « identifier » = l'e-mail de l'acheteur. LECTURE SEULE : le Hub crédite au paiement, retire au remboursement et convertit les seuils en bons nominatifs ; le site n'additionne jamais rien. Réponse : { object: "loyalty", email, actif, points: { solde, seuil, recompense, dureeMois } | null, tampons: { pleins, carte, recompense, canaux, logoUrl } | null, palier: { atteint, prochain, depensesCents, periodeMois } | null, parrainage: { code, recompenseParrain, recompenseFilleul } | null, bons: [ { code, libelle, expireLe } ] }. 409 « capacite_fermee » tant que le site n'a pas déclaré la capacité.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — L'état de fidélité de l'acheteur.
  • 400 — L'identifiant n'est pas un e-mail.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 409 — Capacité « fidelite » fermée.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 10.4 ter.

PATCH /api/v1/customers/{identifier}

Met à jour partiellement un client (seuls les champs présents sont modifiés).

Le corps est validé AVANT la recherche du client (un corps invalide rend 422 même pour un client inexistant). null efface « name », « lastOrderAt » et « source ». Aucun webhook sortant n'est émis.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • identifier (path, obligatoire) — E-mail URL-encodé ou id Hub.
Codes de réponse
  • 200 — Le client mis à jour.
  • 400 — Corps non JSON.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Client introuvable. »
  • 409 — « Un autre client utilise déjà cette adresse e-mail. » (collision d'e-mail dans l'organisation).
  • 422 — « Les données du client sont invalides. »
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.8.

POST /api/v1/customers

Importe ou met à jour un lot de clients (contacts) par e-mail — upsert, 1 à 1000 par appel.

Accepte un tableau brut OU { "customers": [...] }. Doublons d'e-mail dans le lot : le dernier l'emporte, et « total » compte les e-mails DISTINCTS. Création : subscribed=false et source=« site » si absents. Mise à jour : un champ absent ne change rien. En cas d'échec d'une ligne, « errors[].externalRef » porte l'e-mail. « ?dryRun » est refusé en 400 avant la vérification de la clé.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON DISPONIBLE sur « POST /api/v1/customers » : sa seule présence (quelle que soit la valeur) est refusée en 400 par refuserDryRunNonSupporte, AVANT la vérification de la clé. Ne l'envoie pas.
Codes de réponse
  • 200 — Toutes les lignes écrites.
  • 207 — Au moins une ligne a échoué (même corps, « failed » > 0, détail dans « errors »). Rendu aussi si TOUTES ont échoué.
  • 400 — Corps non JSON (« Le corps de la requête doit être un JSON valide. »), ou « ?dryRun » présent.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Données invalides (« Les données des contacts sont invalides. »). « details » provient de flatten().fieldErrors : les erreurs d'une ligne sont rangées sous la clé « customers ».
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.8.
Réserves
  • Non établi depuis le code : errors[].message : c'est le message brut de l'exception (error.message), non reformulé — son contenu exact n'est pas établi.

POST /api/v1/customers/password-reset

Fait envoyer par le Hub l'e-mail « mot de passe oublié » contenant le lien généré par le site.

Le Hub ne gère aucun mot de passe et ne vérifie pas l'existence du compte. Envoi par la configuration d'envoi PROPRE à l'organisation, au nom de la boutique. Nom du destinataire : celui fourni, à défaut celui du contact connu.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — E-mail envoyé.
  • 400 — Corps non JSON.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « Les données de la demande sont invalides. » — « details » via detailsDeZod (clé « _demande » pour une erreur de niveau racine).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 502 — Le fournisseur d'e-mail a refusé l'envoi (« error » = message du fournisseur, à défaut « L'envoi de l'e-mail a échoué. »).
  • 503 — Aucune configuration d'envoi pour l'organisation (envoi bloqué).
Règle d'usage
Master prompt, § 4.8.

POST /api/v1/customers/register

Signale la création d'un compte acheteur : upsert du contact (source « compte ») et scénario de bienvenue s'il est activé.

Répond 200 « registered: true » dans tous les cas où le corps est valide : l'écriture du contact et le scénario sont best-effort (un échec n'est pas signalé). Création : name vide → null, source « compte ». Mise à jour : seul le nom (s'il est non vide) est modifié.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Compte pris en compte.
  • 400 — Corps non JSON.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « Les données du compte sont invalides. » (details = flatten().fieldErrors).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.8.
Réserves
  • Non établi depuis le code : Échec éventuel de runAccountCreatedScenario (non intercepté par la route) : réponse non établie.

POST /api/v1/customers/verify-email

Fait envoyer par le Hub l'e-mail de validation d'adresse contenant le lien généré par le site.

Même modèle que password-reset : le Hub ne valide rien, il envoie au nom de la boutique (type « VALIDATION_ADRESSE » au journal des e-mails).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — E-mail envoyé.
  • 400 — Corps non JSON.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « Les données de la demande sont invalides. » — « details » via detailsDeZod.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 502 — Le fournisseur d'e-mail a refusé l'envoi.
  • 503 — Aucune configuration d'envoi pour l'organisation (envoi bloqué).
Règle d'usage
Master prompt, § 4.8.

POST /api/v1/loyalty/referrals

Un nouveau client présente le code d'un parrain : il reçoit son bon de bienvenue parrainée.

⭐ 16/10/2026 (contrat 2026-10-16.02) — Corps : { code: "PARRAIN-XXXXXX", email, name? }. « ok »: true → { code } (bon nominatif à usage unique, à appliquer comme un code promo) ; « ok »: false → { reason: PARRAINAGE_INACTIF | CODE_INCONNU | AUTO_PARRAINAGE | DEJA_CLIENT | DEJA_PARRAINE | ERREUR, message } (HTTP 200 : un refus n'est pas une panne). Le parrain est récompensé par le Hub, seul, à la 1re commande payée du filleul faite avec ce bon. 409 « capacite_fermee » sans la capacité.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Bon du filleul, ou refus motivé.
  • 400 — Corps invalide.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 409 — Capacité « fidelite » fermée.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 10.4 ter.

Commandes

DELETE /api/v1/orders/{orderNumber}

Supprime définitivement une commande (lignes supprimées en cascade).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • orderNumber (path, obligatoire) — Identifiant de la commande, résolu (après decodeURIComponent) par l'une des trois clés : « id » interne, « orderNumber » (ex. CMD-00985) OU « externalRef » (référence du site). Toujours dans l'organisation de la clé.
Codes de réponse
  • 200 — Commande supprimée.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Commande introuvable dans l'organisation.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.7.

GET /api/v1/orders

Liste les commandes de l'organisation (200 au plus, les plus récemment modifiées d'abord, pas de pagination).

Tri « updatedAt » décroissant, 200 commandes au maximum ; « nextCursor » vaut toujours null. Filtres combinables (ET). Un paramètre inconnu est refusé en 400 (jamais ignoré).

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • status (query) — Statut exact. Valeur hors liste → 400. ⚠️ « CANCELED » avec un seul L.
  • since (query) — Commandes MODIFIÉES depuis cette date (updatedAt ≥ since). Lue par le constructeur Date ; illisible → 400. ISO 8601 recommandé.
  • externalRef (query) — Égalité exacte sur la référence de commande du site.
  • customerEmail (query) — Égalité insensible à la casse sur l'e-mail de l'acheteur (espace client du site).
  • limit (query) — Accepté (non refusé) mais SANS EFFET : la route n'en lit pas la valeur (ni validation, ni pagination) et rend toujours jusqu'à 200 commandes.
  • cursor (query) — Accepté (non refusé) mais SANS EFFET : aucune pagination, « nextCursor » est toujours null.
Codes de réponse
  • 200 — Enveloppe de liste. Si « data » est vide : « warning »: "empty_dataset" et « emptyReason » — "empty_by_design" si un filtre (status, since, externalRef, customerEmail) était posé ou si l'organisation n'a aucune commande, "empty_unexpected" si la liste NON filtrée est vide alors que l'organisation possède des commandes.
  • 400 — Paramètre inconnu (« Filtre inconnu : … », avec la liste acceptée : limit, cursor, customerEmail, externalRef, since, status) ; « status » hors liste (« Le filtre « status » est invalide. ») ; « since » illisible (« Le filtre « since » doit être une date ISO valide. »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.7.
Réserves
  • Non établi depuis le code : data[].carrierStatus : la liste des états bruts que le transporteur peut rendre n'est pas fermée (l'exemple « En route » → « En cours d'acheminement » est une entrée réelle de la table).
  • Non établi depuis le code : customerType, shippingMode, shippingQuoteSource : colonnes texte en base ; seules les valeurs écrites par POST /orders sont établies ici (d'autres écrivains éventuels non vérifiés).
  • Volontairement non détaillé : data[].carrierStatusLabel : table de traduction français des états transporteur (src/lib/carriers/sendcloud.ts, etatEnFrancais) — un état inconnu est rendu tel quel.

GET /api/v1/orders/{orderNumber}

Lit une commande par son id interne, son numéro Hub OU sa référence externe.

Rend la même sélection de champs que la liste des commandes, plus les informations de transport exposées (labelBoughtAt, carrierStatus, carrierStatusLabel, carrierCheckedAt).

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • orderNumber (path, obligatoire) — Identifiant de la commande, résolu (après decodeURIComponent) par l'une des trois clés : « id » interne, « orderNumber » (ex. CMD-00985) OU « externalRef » (référence du site). Toujours dans l'organisation de la clé.
Codes de réponse
  • 200 — La commande.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Aucune commande de l'organisation ne porte cet id, ce numéro ni cette référence (« Commande introuvable. »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.7.
Réserves
  • Non établi depuis le code : Valeurs possibles de shippingMode, shippingQuoteSource, shippingMethodCode, shippingCarrier : chaînes libres en base, non énumérées.
  • Non établi depuis le code : customerType : chaîne libre en base (défaut PARTICULIER) ; la création valide PARTICULIER | PRO, d'autres valeurs historiques ne sont pas exclues.
  • Volontairement non détaillé : data.carrierStatusLabel : table de traduction ETATS_EN_FRANCAIS (src/lib/carriers/sendcloud.ts) non recopiée ; un état inconnu est rendu tel quel.

PATCH /api/v1/orders/{orderNumber}

Met à jour le STATUT d'une commande (et, facultativement, le moyen de paiement et le suivi du colis).

Ordre des contrôles : JSON (400) → « status » (422) → « paymentMethod » (422) → existence (404). Effets : passage à PAID (premier) → paidAt horodaté, facture émise, colis créé si le gérant l'a demandé ; SHIPPED (premier) → shippedAt horodaté ; REFUNDED → avoir du SOLDE (facture − avoirs déjà émis ; rien s'il est nul — contrat 2026-09-29.16), puis refundedCents porté au total ; stock resynchronisé ; scénarios marketing si le statut change. ⭐ Un remboursement PARTIEL fait chez ton prestataire se déclare par « POST /api/v1/orders/{orderNumber}/refunds » (contrat 2026-09-29.18), pas ici. Si aucune trackingUrl n'est fournie ni déjà enregistrée, le Hub la déduit du transporteur + numéro (modèle du gérant). Aucun webhook sortant (anti-boucle). La réponse est RELUE après toutes les écritures (compteur de remboursement compris).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • orderNumber (path, obligatoire) — Identifiant de la commande, résolu (après decodeURIComponent) par l'une des trois clés : « id » interne, « orderNumber » (ex. CMD-00985) OU « externalRef » (référence du site). Toujours dans l'organisation de la clé.
Codes de réponse
  • 200 — La commande après mise à jour (même forme que GET).
  • 400 — Corps illisible (« Le corps de la requête doit être un JSON valide. »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Commande introuvable dans l'organisation.
  • 409 — ⭐ 06/10/2026 — « parcels » envoyé alors que le Hub a déjà créé les étiquettes de cette commande chez un transporteur : leurs numéros font foi (annule-les d'abord depuis le Hub).
  • 422 — « status » absent ou hors liste : details.status liste les valeurs acceptées et rappelle « CANCELED » (un L). Ou « paymentMethod » invalide (« Le champ « paymentMethod » est invalide. », sans details). Ou « parcels » mal formé (details.parcels nomme l'entrée fautive).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.7.
Réserves
  • Non établi depuis le code : Format exact de la trackingUrl déduite (resolveTrackingUrl + modèle du gérant) : non lu.

POST /api/v1/orders

Enregistre une commande passée sur le site (totaux recalculés par le Hub, idempotente sur « externalRef »).

ORDRE DES CONTRÔLES (lu dans le code) : 1) « ?dryRun » d'une valeur autre que « 1 » / « true » → 400 (AVANT même la clé) ; 2) clé en écriture ; 3) JSON illisible → 400 ; 4) schéma → 422 ; 5) CARTE non payée → 402 ; 6) pays puis code postal → 422 ; 7) mode de livraison interdit pour un produit → 422 ; 8) créneau de retrait → 422 si non payée ; 9) quantité mesurée refusée → 422 ; 10) code de remise invalide → 422 si non payée ; 11) remise > marchandise + port → 422 ; 12) « externalRef » déjà connu → 200 « existing »: true ; 13) « ?dryRun=1 » → 200 « orders.dry-run », RIEN d'écrit ; 14) création → 201. ⚠️ Le rejeu idempotent (étape 12) n'intervient qu'APRÈS les contrôles 5 à 11 : un rejeu repasse par eux. Les refus 6, 7, 9 et 11 s'appliquent même à une commande déjà payée ; seuls le créneau (8) et le code de remise (10) cèdent devant un paiement confirmé. « Paiement confirmé » = « payment.confirmed »: true OU « status »: "PAID" → commande créée en PAID (paidAt = payment.paidAt ou maintenant) et facture émise ; sinon PENDING. Après création (au mieux, sans faire échouer la réponse) : contact acheteur mis à jour, stock décompté (peut passer sous zéro), facture et colis automatique si payée, e-mail de confirmation à l'acheteur et alerte au gérant, code de remise consommé, scénarios marketing « commande créée ».

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — ⭐ 29/09/2026 (contrat « 2026-09-29.11 ») — ESSAI À BLANC : « 1 » ou « true ». La commande est jugée EN ENTIER (mêmes refus, mêmes codes qu'en réel), puis rien n'est écrit : ni commande, ni numéro, ni stock, ni e-mail, ni évènement. Réponse 200 { object: « orders.dry-run », dryRun: true, ecrit: false, verdict: « ACCEPTEE », totalCents, avertissements éventuels }. Toute autre valeur → 400, rien n'est écrit.
Codes de réponse
  • 200 — IDEMPOTENCE : une commande de cette organisation porte déjà cet « externalRef » (constaté avant l'écriture, ou par collision d'unicité en concurrence). Rien n'est créé ni envoyé ; la commande existante est rendue. ⚠️ Seuls « object », « data », « existing » : PAS de « ignoredFields » ni d'avertissement.
  • 201 — Commande créée. « existing »: false et « ignoredFields » sont TOUJOURS présents. Champs d'avertissement présents seulement dans leur cas (voir chaque propriété) ; aucun n'est un refus.
  • 400 — « ?dryRun » présent dans l'URL (refusé, rien n'est écrit) ; ou corps illisible (« Le corps de la requête doit être un JSON valide. »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — RÈGLE « commande carte = paiement encaissé » : paymentMethod = "CARD" sans « payment.confirmed »: true ni « status »: "PAID". AUCUNE écriture. Ne pas rappeler la route : ramener l'acheteur à son panier. (Contrôle suspendable par l'exploitant côté serveur ; actif par défaut.) À distinguer du 402 « subscription_required » de l'authentification : ici le champ « code » vaut « card_payment_not_confirmed ».
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — ⚠⚠ « message » s'adresse à toi, l'intégrateur ; « messagePourAcheteur » est prêt à afficher tel quel à un consommateur. N'affiche JAMAIS « message » ni « error » à un acheteur. Trois de ces refus tombent PENDANT un paiement et l'acheteur peut les provoquer lui-même (son code postal, son créneau, son code de remise) : ceux-là portent « messagePourAcheteur » à la RACINE du corps, à côté de « error ». Plusieurs formes selon le motif (champ « error » toujours présent, en français) : • SCHÉMA : error « Les données de la commande sont invalides. », details = erreurs par champ de RACINE (Record<string, string[]> ; une erreur dans items[n] ou shipping.* est rangée sous « items » / « shipping »). Corps non objet (tableau, null) → details = {}. • PAYS : details { code: country_not_served | country_forbidden, deblocable }. • CODE POSTAL (si une livraison est déclarée) : details { code: postal_code_required | postal_code_refused, deblocable } — + « messagePourAcheteur ». • EXPÉDITION FERMÉE (« shipping.open » faux, livraison demandée, paiement NON confirmé) : details { code: "shipping_closed" } — + « messagePourAcheteur ». Paiement CONFIRMÉ : 201 avec « warningDeliveryMode: shipping_closed ». • MODE DE LIVRAISON (paiement NON confirmé) : details { code: "product_delivery_mode_refused", items: string[], products: string[] } — un produit restreint à certains modes (ex. retrait seul) — + « messagePourAcheteur », TOUJOURS. Paiement CONFIRMÉ : 201 avec « warningDeliveryMode ». • CRÉNEAU DE RETRAIT (mode RETRAIT + pickupSlot.start, paiement NON confirmé, externalRef pas déjà connu) : details { code: "pickup_slot_invalid", nextSlots: [{ date, from, to, remaining }] } (5 au plus ; vide si point absent ou étranger à la boutique) — + « messagePourAcheteur », TOUJOURS (les trois motifs du juge l'ont : créneau non proposable ou complet, retrait sans point, point inconnu de la boutique). • QUANTITÉ MESURÉE : details { items: string[] } — une phrase par ligne refusée. • CODE DE REMISE invalide, paiement NON confirmé : details { discountCode: string[] } — + « messagePourAcheteur ». ⚠ « details.discountCode » est justement la forme qu'un formulaire rend SOUS le champ de saisie : ces phrases-là tutoient et nomment nos routes, ne les y mets pas. • REMISE TROP FORTE (discountCents > subtotalCents recalculé + shippingCents) : details { discountCents: string[] } — le plafond est donné. • DATE D'ENCAISSEMENT hors bornes (seulement si le paiement est CONFIRMÉ et « payment.paidAt » fourni) : details { code: "paid_at_dans_le_futur" | "paid_at_trop_ancien", recu } — « recu » rend la date telle que vous l'avez envoyée. Bornes réelles lues dans le code : au plus 5 MINUTES dans le futur (tolérance d'horloge entre votre serveur et le nôtre), au plus 400 JOURS dans le passé (un peu plus d'un exercice). ⚠️ Le Hub REFUSE, il ne corrige pas en silence : cette date fixe l'exigibilité de la TVA et date la facture, et la remplacer par « maintenant » ferait une facture juste avec une date fausse. N'envoyez pas « payment.paidAt » si vous ne l'avez pas — le Hub prend alors l'instant présent.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 500 — Numéro de commande introuvable après 3 tentatives (collisions) : « La création de la commande a échoué. Réessayez. ».
Règle d'usage
Master prompt, § 4.7.
Réserves
  • Non établi depuis le code : Corps de la réponse en cas d'exception non interceptée (erreur de base hors collision d'unicité) : 500 rendu par le cadre applicatif, forme non établie.
  • Non établi depuis le code : Contrôle pays/code postal : sauté si la lecture des réglages de l'organisation échoue (repli silencieux lu dans le code).
  • Non établi depuis le code : data de la réponse 201 : relue après création ; le cas théorique d'une relecture nulle n'est pas traité par la route.
  • Volontairement non détaillé : Textes des messages d'erreur et des « …Hint » / « quoiFaire » : phrases françaises écrites POUR L'INTÉGRATEUR (elles tutoient, elles nomment nos champs et nos routes) — à lire, pas à tester (tester les champs « code »), et JAMAIS à relayer à un acheteur : c'est « messagePourAcheteur » qui s'affiche.
  • Volontairement non détaillé : 422 SCHÉMA : messages Zod par champ (src/lib/validations/order.ts).
  • Volontairement non détaillé : 422 QUANTITÉ MESURÉE : motifs de refus de quantiteAcceptable (pas, minimum, maximum, quantité manquante) — src/lib/vente-a-la-mesure.ts.
  • Volontairement non détaillé : 422 CODE DE REMISE : motifs de validerCode (expiré, désactivé, épuisé, nominatif…) — src/lib/discount-codes.ts.
  • Volontairement non détaillé : Règle exacte d'autorisation d'un mode par produit (modeCommandeAutorise, src/lib/livraison-produit.ts) et liste des pays interdits/déconseillés (src/lib/zones-risque.ts).
  • Volontairement non détaillé : Effets secondaires après création (e-mails, facture, colis automatique, scénarios marketing, stock) : non visibles dans la réponse.

POST /api/v1/orders/{orderNumber}/refunds

DÉCLARE un remboursement (partiel ou total) fait chez TON prestataire de paiement : le Hub émet l'avoir. Aucun appel au prestataire.

Contrat 2026-09-29.18. ⛔ DÉCLARATION SEULEMENT : le Hub n'appelle aucun prestataire et ne détient pas ta clé — tu rembourses chez lui, puis tu déclares ici. Ordre des contrôles : « ?dryRun » invalide (400) → clé d'ÉCRITURE → JSON (400) → corps (422, « details.code » nommé) → commande de TON organisation (404) → REJEU par « providerRefundId » (200 « existing » / 409) → commande payée (422 order_not_paid) → facture présente (422 invoice_missing) → montant ≤ restant (422 amount_exceeds_remaining). Effets (201) : un AVOIR du montant (« PARTIAL », ou « TOTAL » si ce seul geste couvre toute la facture), imprimant « reason » et le moyen de remboursement ; refundedCents += amountCents (plafonné au total) ; lastRefundAt = refundedAt (ou maintenant). Quand la somme des avoirs atteint la facture : statut REFUNDED, refundedCents au total, stock rendu UNE fois, scénarios — les mêmes effets que PATCH { status: REFUNDED } ; sinon le statut ne change pas. Un « PATCH { status: REFUNDED } » envoyé ensuite n'émet aucun avoir de plus (solde nul). ⭐ IDEMPOTENCE : le même « providerRefundId » (unique par organisation) rend 200 « existing »: true et le MÊME avoir, sans rien écrire — rejoue sans crainte le webhook de ton prestataire. Sans « providerRefundId », chaque appel est un NOUVEAU remboursement. « ?dryRun=1 » (ou « true ») : tout est jugé, rien n'est écrit (ni avoir, ni numéro, ni compteur, ni statut). Évènements : « invoice.credited » (l'avoir) ; AUCUN « order.updated » ne te revient — c'est toi qui viens de l'écrire (anti-boucle, comme le PATCH).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • orderNumber (path, obligatoire) — Identifiant de la commande, résolu (après decodeURIComponent) par l'une des trois clés : « id » interne, « orderNumber » (ex. CMD-00985) OU « externalRef » (référence du site). Toujours dans l'organisation de la clé.
  • dryRun (query) — Essai à blanc : tout est jugé, rien n'est écrit. Toute autre valeur → 400.
Codes de réponse
  • 200 — « existing »: true — REJEU : ce « providerRefundId » est déjà déclaré sur cette commande, pour ce montant. Même forme que 201, même avoir, rien d'écrit. Ou, avec « ?dryRun=1 » : « object »: « refund.dry-run », « ecrit »: false, « verdict »: « ACCEPTE », « creditNote.number »: null et les valeurs QUI SERAIENT écrites.
  • 201 — Remboursement déclaré : l'avoir est émis, le compteur a avancé.
  • 400 — Corps illisible, ou « dryRun » autre que « 1 » / « true ». Rien n'a été écrit.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Commande introuvable dans TON organisation (une commande d'une autre boutique rend aussi 404).
  • 409 — « details.code » : « provider_refund_id_conflict » (cet identifiant est déjà déclaré sur une AUTRE commande) ou « provider_refund_id_amount_mismatch » (déjà déclaré avec un autre montant ; « details.declaredAmountCents »). Rien n'a été écrit.
  • 422 — « details.code » : « invalid_body » · « invalid_amount » · « invalid_reason » · « invalid_provider_refund_id » · « invalid_refunded_at » · « invalid_refund_method » · « order_not_paid » (paidAt nul) · « invoice_missing » (aucune facture) · « amount_exceeds_remaining » (« details.remainingCents », « details.invoiceTotalCents », « details.creditedCents »). Rien n'a été écrit.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 6.2 bis.

POST /api/v1/orders/rejected-notice

Envoie à un acheteur l'e-mail qui lui dit que sa commande n'a pas pu être retenue, et pourquoi.

Deux preuves cumulées : la clé d'API (Authorization: Bearer) prouve le site ; le jeton du chemin prouve l'acheteur. La commande est cherchée par { pickupRescheduleToken: jeton, organizationId: organisation de la clé }. L'e-mail part avec la configuration d'envoi DE LA BOUTIQUE (son domaine, son nom), jamais la nôtre. ⚠️ LE MOTIF EST OBLIGATOIRE ET DOIT ÊTRE UNE PHRASE : un code technique (« pickup_slot_invalid ») est refusé en 422. L'acheteur doit comprendre ce qui s'est passé et ce qu'il peut faire — c'est tout l'objet de cet envoi.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — L'e-mail est parti.
  • 400 — Le corps n'est pas un JSON valide.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Demande invalide, ou motif ressemblant à un code technique. AUCUN e-mail n'a été envoyé.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 502 — L'envoi a échoué chez le fournisseur d'e-mails de la boutique.
  • 503 — L'envoi est bloqué (configuration d'e-mails incomplète ou domaine non vérifié).
Règle d'usage
Master prompt, § 6.2 ter.

Comptoir

GET /api/v1/comptoir/categories

Les catégories DU COMPTOIR (vente et devis du Hub), dans leur ordre, avec leurs produits.

⭐ 15/10/2026 (contrat 2026-10-15.01) — le commerçant range ses produits vendus au comptoir dans SES catégories (« Magasin », « Marché du samedi », « Plateaux prêts »…) ; au comptoir et dans les devis du Hub, un bouton par catégorie. ⛔ CE NE SONT PAS les catégories de la boutique en ligne (/api/v1/categories) : NE LES AFFICHE JAMAIS sur le site. Réponse : « data.categories »: [ { id, name, position, products: [ { productId, externalRef, name } ] } ].

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Les catégories du comptoir.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 10.4 bis.

POST /api/v1/comptoir

Enregistre une vente faite hors du site et émet sa facture, dans la même série que les ventes en ligne.

⭐ DEUX CHEMINS, TOUJOURS LES DEUX : le commerçant saisit depuis son Hub, OU son agent pousse depuis son système (caisse de boutique, carnet de tournée). ⛔ N'éteins jamais son écran : le jour où tu n'es pas joignable, il doit pouvoir facturer lui-même. ⭐ CE QUE ÇA FAIT : une commande DÉJÀ PAYÉE entre dans les commandes (elle compte dans le chiffre d'affaires et l'e-reporting), et la facture est émise avec le numéro suivant de la série — même format, même Factur-X, même dépôt à la plateforme agréée qu'une vente du site. ⚠️ AUCUN TRANSPORTEUR n'est déclaré : la marchandise est partie avec l'acheteur, il n'y a rien à expédier. ⚠️ « clientNom » est obligatoire — une facture sans destinataire n'est pas une facture ; « Client de passage » convient pour une vente au comptant. ⚠️ « tauxTva » est PAR LIGNE, jamais un taux unique : un commerce qui vend à 20 % et à 5,5 % aurait une TVA fausse. ⭐ « ?dryRun » est HONORÉ et ne consomme AUCUN numéro de facture — un numéro consommé ne se réattribue jamais. ⛔⛔ RIEN N'EST AVALÉ EN SILENCE, ET RIEN N'EST DEVINÉ (durci le 17/09/2026) : une quantité doit être un NOMBRE STRICTEMENT POSITIF d'au plus trois décimales (ni texte, ni zéro, ni négatif — 0,5 est accepté depuis le 29/09/2026), un prix un ENTIER DE CENTIMES ZÉRO OU PLUS — un montant négatif serait un AVOIR, et un avoir ne s'émet pas par cette route. « clientNom » et « reglement » sont refusés absents comme ils l'étaient déjà faux. ⭐ UNE SEULE LIGNE FAUTIVE REFUSE TOUT LE CORPS, avec « ligne_refusee » qui NOMME chacune : facturer la partie valide serait pire que refuser — un refus se voit et se refait, une facture amputée part chez l’acheteur et ne se corrige que par un avoir. ⭐ ET « ?dryRun » REFUSE EXACTEMENT CE QUE LE MODE RÉEL REFUSE : un essai à blanc qui accepterait ce que le réel rejette donnerait confiance avant l’échec. ⭐⭐ LIGNE DE CATALOGUE (contrat 2026-09-29.06) : { productExternalRef | productId, quantite } — sans « prixUnitaireCents », le Hub facture « priceCents » + « ecoTaxCents » du produit (TTC tout compris, éco-participation NOMMÉE sur la facture) ; à la mesure, « quantite » est dans l’unité du prix de référence (0.5 = 0,5 kg). ⛔⛔ LE STOCK EST RETIRÉ PAR LE HUB, à la décimale, pour chaque ligne de catalogue d’un produit suivi (« trackInventory ») quand la boutique a « stockTracking » vrai ; jamais pour une ligne libre ; jamais un refus pour cause de stock. Ne le décrémente JAMAIS toi-même après cet appel : la marchandise partirait deux fois.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — ⭐ HONORÉ : tout est vérifié, AUCUNE vente, AUCUNE facture, AUCUN numéro consommé.
Codes de réponse
  • 200 — Essai à blanc (« ?dryRun ») : « detail » (les lignes telles qu'elles seraient facturées) et « totalCents », le total EXACT que la facture imprimerait (somme des lignes, en centimes entiers, demi vers le haut). Rien n'est émis.
  • 201 — « orderId », « orderNumber », « factureId », « factureNumero ».
  • 400 — « json_invalide », « ligne_refusee » (chaque ligne nommée), « produit_introuvable », « reglement_manquant », « reglement_inconnu » ou « vente_refusee ».
  • 401 — Clé d'API absente ou invalide. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 10.4.

PUT /api/v1/comptoir/categories

REMPLACE l'ensemble des catégories du comptoir (déclaratif, rejouable sans doublon).

⭐ Corps : { « categories »: [ { « name » (≤ 40 caractères, unique), « products »: [productId | externalRef, …] } ] } — 100 catégories au plus ; l'ordre de la liste est l'ordre des boutons. ⚠️ REMPLACE L'ENSEMBLE : une catégorie absente est SUPPRIMÉE (ses produits restent au catalogue) ; « categories »: [] les retire toutes. Rejouer le même envoi ne change rien. ⭐ Un produit inconnu (« produitsInconnus ») ou non vendu au comptoir (« produitsHorsComptoir ») est IGNORÉ et NOMMÉ — passe « soldAtCounter »: true sur sa fiche puis renvoie. ⛔ Ne les affiche jamais sur le site : elles n'existent qu'au comptoir du Hub.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — « data » : categories, produitsRanges, produitsInconnus, produitsHorsComptoir (et « aFaire » s'il y en a).
  • 400 — categories_requises · trop_de_categories · nom_requis · nom_trop_long · nom_en_double · products_invalide
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 10.4 bis.

Configuration

DELETE /api/v1/config/{nom}

Supprime la ligne dont la clé naturelle vaut « ?cle= ».

Zones de livraison : « carrier » (code du parent) obligatoire. Supprimer un transporteur supprime aussi ses zones (suppression en cascade en base). Notifie le site (« catalog.settings_changed », « deleted: true »). Paramètres contrôlés AVANT la clé.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • nom (path, obligatoire) — Nom de la collection. Autre valeur : 404. Les chemins réels, et la CLÉ NATURELLE à employer pour écrire ou supprimer une ligne (c'est elle qu'on envoie, jamais l'« id ») : · /api/v1/config/points-retrait — clé « name » (le nom du point). Écriture : POST avec l'objet du point ({ name, addressLine, slots, seasons… }) ; suppression : DELETE /api/v1/config/points-retrait?cle=<name> (29/09/2026 : la forme « /{name} » écrite ici était FAUSSE et répondait 404 — c'est la même forme « ?cle= » pour TOUTES les collections de /config). ⚠️ Il n'existe PAS de « PATCH /config/points-retrait/{id} » : un POST sur une clé existante MET À JOUR. · /api/v1/config/zones-livraison — clé « label », et « carrier » (le CODE du transporteur) est obligatoire. · /api/v1/config/transporteurs — clé « code ». · /api/v1/config/campagnes — clé « name ». · /api/v1/config/scenarios — clé « scenarioKey ». · /api/v1/config/e-reporting — clé « periodKey ». · /api/v1/config/annonces — clé « cle » (celle que TU choisis). Les annonces du bandeau, plusieurs, chacune avec ses dates ; lues par le site dans « banners[] » de GET /api/v1/webhook. · /api/v1/config/blocs-de-page — clé « cle » (celle que TU choisis au câblage : « accueil-accroche », « a-propos », « faq-generale »…). Les textes éditoriaux des pages ; lus par le site dans « pageBlocks[] » de GET /api/v1/webhook. ⛔ Pas pour les textes juridiques (CGV, CGU, confidentialité). ⭐ La liste complète, avec les champs acceptés de chaque collection, est rendue par GET /api/v1/config.
  • cle (query, obligatoire) — Valeur de la clé naturelle (« key ») de la ligne. Absente ou vide : 422.
  • carrier (query) — Obligatoire pour zones-livraison (code du transporteur parent) ; sans effet ailleurs.
  • deploy (query) — REGROUPER UN LOT D'ÉCRITURES (24/09/2026). Absent : comportement inchangé — le site est prévenu et le Deploy Hook part comme avant, et la réponse ne porte AUCUNE clé « deploy ». « 0 » : écrit et prévient le site, SANS appeler le Deploy Hook (à poser sur chaque écriture du lot sauf la dernière) ; la réponse rend « deploy »: « differe ». « 1 » : appelle le Deploy Hook une fois, même si « siteCapabilities.rebuildOn » ne réclamait pas cet évènement (à poser sur la DERNIÈRE écriture) ; la réponse rend l'issue réelle. ⛔ Toute autre valeur : 422, et RIEN n'est écrit.
  • limit (query) — Accepté par le contrôle des paramètres (refuserFiltresInconnus l'admet d'office) mais NON LU par la route : aucun effet.
  • cursor (query) — Accepté par le contrôle des paramètres (refuserFiltresInconnus l'admet d'office) mais NON LU par la route : aucun effet. La liste n'est pas paginée (« nextCursor » vaut toujours null quand il est présent).
Codes de réponse
  • 200 — Ligne supprimée.
  • 400 — Paramètre d'URL inconnu : le message nomme le(s) paramètre(s) refusé(s) et liste ceux que la route accepte (« Filtre inconnu : … »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Collection inconnue (voir GET), ou aucune ligne de cette clé dans l'organisation (« Aucun « X » dans « nom » chez toi. »).
  • 422 — « ?cle= » absent ou vide (sans « details ») ; ou, pour zones-livraison, Zones de livraison seulement : « carrier » absent ou vide (« « carrier » est obligatoire : une zone appartient à un transporteur. »), ou aucun transporteur de ce code dans l'organisation (« Aucun transporteur « X » chez toi. ») ; « details.carrier » dit quoi faire.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).

GET /api/v1/config

L'index des collections de réglage : pour chacune, sa clé naturelle, son parent et ses champs avec types et bornes.

N'accepte AUCUN paramètre d'URL, pas même « limit » ni « cursor » (refuserFiltresInconnus avec pagine: false). Le contrôle des paramètres a lieu AVANT celui de la clé.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Les 8 collections, triées par nom.
  • 400 — Paramètre d'URL refusé : « limit » ou « cursor » (« Cette route ne pagine pas : elle rend toujours la liste complète… »), ou tout autre paramètre (« Filtre inconnu : … »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Réserves
  • Volontairement non détaillé : data[].quoi : textes abrégés par « … » dans l'exemple (le Hub rend le texte intégral).

GET /api/v1/config/{nom}

Rend TOUTES les lignes d'une collection de réglage de l'organisation, triées par clé naturelle (pas de pagination).

Zones de livraison : sans « carrier », TOUTES les zones de l'organisation ; avec « carrier », celles de ce transporteur. Les zones portent en plus « carrier » (code du parent) ; les points de retrait portent « nextSlots » et la réponse porte « timezone ».

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • nom (path, obligatoire) — Nom de la collection. Autre valeur : 404. Les chemins réels, et la CLÉ NATURELLE à employer pour écrire ou supprimer une ligne (c'est elle qu'on envoie, jamais l'« id ») : · /api/v1/config/points-retrait — clé « name » (le nom du point). Écriture : POST avec l'objet du point ({ name, addressLine, slots, seasons… }) ; suppression : DELETE /api/v1/config/points-retrait?cle=<name> (29/09/2026 : la forme « /{name} » écrite ici était FAUSSE et répondait 404 — c'est la même forme « ?cle= » pour TOUTES les collections de /config). ⚠️ Il n'existe PAS de « PATCH /config/points-retrait/{id} » : un POST sur une clé existante MET À JOUR. · /api/v1/config/zones-livraison — clé « label », et « carrier » (le CODE du transporteur) est obligatoire. · /api/v1/config/transporteurs — clé « code ». · /api/v1/config/campagnes — clé « name ». · /api/v1/config/scenarios — clé « scenarioKey ». · /api/v1/config/e-reporting — clé « periodKey ». · /api/v1/config/annonces — clé « cle » (celle que TU choisis). Les annonces du bandeau, plusieurs, chacune avec ses dates ; lues par le site dans « banners[] » de GET /api/v1/webhook. · /api/v1/config/blocs-de-page — clé « cle » (celle que TU choisis au câblage : « accueil-accroche », « a-propos », « faq-generale »…). Les textes éditoriaux des pages ; lus par le site dans « pageBlocks[] » de GET /api/v1/webhook. ⛔ Pas pour les textes juridiques (CGV, CGU, confidentialité). ⭐ La liste complète, avec les champs acceptés de chaque collection, est rendue par GET /api/v1/config.
  • carrier (query) — Code d'un transporteur : filtre les zones-livraison sur ce parent (inconnu : 422). Sur les autres collections : accepté, sans effet.
Codes de réponse
  • 200 — La collection.
  • 400 — Paramètre d'URL refusé : « limit » ou « cursor » (« Cette route ne pagine pas : elle rend toujours la liste complète… »), ou tout autre paramètre (« Filtre inconnu : … »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « {nom} » n'est pas une collection de réglage du Hub. Rendu AVANT le contrôle de la clé et des paramètres.
  • 422 — Zones de livraison seulement : « carrier » absent ou vide (« « carrier » est obligatoire : une zone appartient à un transporteur. »), ou aucun transporteur de ce code dans l'organisation (« Aucun transporteur « X » chez toi. ») ; « details.carrier » dit quoi faire.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Réserves
  • Volontairement non détaillé : data[].slots, closedDays, extraDays, faq, seasons (points-retrait), freeShippingOverrides (transporteurs), config (scenarios) : JSON rendu tel qu'enregistré.

POST /api/v1/config/{nom}

Crée OU met à jour une ligne, retrouvée par sa clé naturelle (rejouable sans doublon).

Le schéma dépend de « {nom} » (voir oneOf, un par collection). La clé naturelle est TOUJOURS exigée ; les autres champs obligatoires seulement à la création ; en mise à jour, un champ absent n'est pas touché. REFUS GLOBAL : rien n'est écrit au premier défaut. Notifie le site (« catalog.settings_changed », portée « config.<nom> »). La ligne rendue n'a PAS les enrichissements du GET (ni « carrier » pour une zone, ni « nextSlots »).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • nom (path, obligatoire) — Nom de la collection. Autre valeur : 404. Les chemins réels, et la CLÉ NATURELLE à employer pour écrire ou supprimer une ligne (c'est elle qu'on envoie, jamais l'« id ») : · /api/v1/config/points-retrait — clé « name » (le nom du point). Écriture : POST avec l'objet du point ({ name, addressLine, slots, seasons… }) ; suppression : DELETE /api/v1/config/points-retrait?cle=<name> (29/09/2026 : la forme « /{name} » écrite ici était FAUSSE et répondait 404 — c'est la même forme « ?cle= » pour TOUTES les collections de /config). ⚠️ Il n'existe PAS de « PATCH /config/points-retrait/{id} » : un POST sur une clé existante MET À JOUR. · /api/v1/config/zones-livraison — clé « label », et « carrier » (le CODE du transporteur) est obligatoire. · /api/v1/config/transporteurs — clé « code ». · /api/v1/config/campagnes — clé « name ». · /api/v1/config/scenarios — clé « scenarioKey ». · /api/v1/config/e-reporting — clé « periodKey ». · /api/v1/config/annonces — clé « cle » (celle que TU choisis). Les annonces du bandeau, plusieurs, chacune avec ses dates ; lues par le site dans « banners[] » de GET /api/v1/webhook. · /api/v1/config/blocs-de-page — clé « cle » (celle que TU choisis au câblage : « accueil-accroche », « a-propos », « faq-generale »…). Les textes éditoriaux des pages ; lus par le site dans « pageBlocks[] » de GET /api/v1/webhook. ⛔ Pas pour les textes juridiques (CGV, CGU, confidentialité). ⭐ La liste complète, avec les champs acceptés de chaque collection, est rendue par GET /api/v1/config.
  • dryRun (query) — N'existe pas sur « config/<nom> » : sa seule présence (quelle que soit sa valeur) rend 400 AVANT toute écriture (refuserDryRunNonSupporte). Il n'existe que sur « POST /api/v1/products ».
  • deploy (query) — REGROUPER UN LOT D'ÉCRITURES (24/09/2026). Absent : comportement inchangé — le site est prévenu et le Deploy Hook part comme avant, et la réponse ne porte AUCUNE clé « deploy ». « 0 » : écrit et prévient le site, SANS appeler le Deploy Hook (à poser sur chaque écriture du lot sauf la dernière) ; la réponse rend « deploy »: « differe ». « 1 » : appelle le Deploy Hook une fois, même si « siteCapabilities.rebuildOn » ne réclamait pas cet évènement (à poser sur la DERNIÈRE écriture) ; la réponse rend l'issue réelle. ⛔ Toute autre valeur : 422, et RIEN n'est écrit.
Codes de réponse
  • 200 — Ligne existante mise à jour (« created »: false) ; même forme que 201.
  • 201 — Ligne créée (« created »: true).
  • 400 — Corps JSON illisible ; corps qui n'est pas un objet ; ou « ?dryRun » présent.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « {nom} » n'est pas une collection de réglage du Hub. Rendu AVANT le contrôle de la clé et des paramètres.
  • 409 — Campagnes : la campagne existe et n'est plus DRAFT — elle ne se modifie plus par l'API. « details.raison » : tableau de phrases.
  • 422 — Trois cas : (1) clé naturelle absente ou vide — « details » = { <clé>: [phrase] } ; (2) Zones de livraison seulement : « carrier » absent ou vide (« « carrier » est obligatoire : une zone appartient à un transporteur. »), ou aucun transporteur de ce code dans l'organisation (« Aucun transporteur « X » chez toi. ») ; « details.carrier » dit quoi faire. ; (3) valeurs refusées — « Réglages refusés : aucun n'a été enregistré. » avec « details » TABLEAU de phrases.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 500 — Erreur non interceptée par la route (exception de la base remontée telle quelle) — corps non établi.
Réserves
  • Non établi depuis le code : null (ou texte vide) sur une colonne non nullable (transporteurs.position ; zones-livraison.position ; points-retrait.country, preparationHours, priceCents, displayOrder ; campagnes.subject, fromName, fromEmail, htmlContent, segmentType ; e-reporting.declaredAt) et null sur une colonne JSON : acceptés par la validation, l'écriture devrait échouer (500). Non éprouvé.
  • Non établi depuis le code : Création d'une campagne sans « subject », « fromName », « fromEmail » ou « htmlContent » (colonnes obligatoires sans défaut) : devrait échouer (500). Non éprouvé.
  • Volontairement non détaillé : Formes exactes acceptées par verifierEcritureFaq, verifierEcritureSaisons et verifierEcriturePhoto (points-retrait) : non décrites ici.

Contrat

GET /api/v1/master-prompt

Le master prompt À JOUR, avec les valeurs de TA boutique. Ne contient jamais de jeton d'enrôlement.

Rend le document de branchement et de mise à jour tel qu'il est aujourd'hui. ⚠️ Il ne remplace pas « contract.pending » (GET /api/v1/webhook), qui reste ce que tu déclares et ce que nous mesurons : cette route est la référence complète, à relire. Ne la mets pas en cache. « ?format=texte » rend le texte brut en text/plain ; tout autre paramètre est refusé en 400.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • format (query) — « texte » rend le document brut (text/plain; charset=utf-8) au lieu du JSON.
  • entree (query) — Version d'une entrée de contrat (« 2026-09-17.03 »). Rend { object: "contract_entry", version, titre, critique, instructions, contractVersion } — appliquée ou non. ⭐ Elle existe pour que la promesse « une entrée déclarée ne change jamais » soit VÉRIFIABLE : une garantie invérifiable n'est pas une garantie. Version inconnue : 404 « entree_inconnue », avec les bornes (première, dernière, nombre) et jamais la liste entière.
Codes de réponse
  • 200 — Le document, et les deux versions de contrat qui te situent.
  • 400 — Un paramètre d'URL non déclaré a été envoyé.
  • 401 — Clé d'API absente, invalide ou révoquée. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).

Déploiement

POST /api/v1/deploy/confirmed

Le site déclare la version qu'il sert réellement après sa reconstruction (ou son échec).

À appeler une fois le nouveau contenu RÉELLEMENT servi. Le Hub enregistre et horodate ce qui est déclaré, sans juger la version. « deployed » vaut false UNIQUEMENT si le corps porte le booléen false ; toute autre valeur ou absence vaut true. En échec (false), la dernière version confirmée est conservée et seul le détail est remplacé. Corps illisible = traité comme vide (422 missing_version). Paramètres d'URL : ignorés (« ?dryRun » n'est pas contrôlé ici).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Confirmation enregistrée.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Organisation de la clé introuvable : { "error": "organization_not_found" } (sans « message »).
  • 422 — « version » absente / vide / non-chaîne (error « missing_version »), ou de plus de 200 caractères (error « version_too_long »). Forme propre : { error: <code>, message: <explication en français> }.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 1.

POST /api/v1/reconcile

Provoque une passe de réconciliation du catalogue et rend son résultat, avec l'état du Deploy Hook et de son rejeu.

⚠️ La passe est GLOBALE : elle parcourt toutes les organisations ACTIVE/TRIAL qui ont un Deploy Hook, pas seulement celle de la clé ; « organizationsChecked » et « sitesTriggered » comptent sur l'ensemble. « siteTriggered » dit si le site de CETTE organisation a été redéclenché (date du dernier appel du hook changée). Aucun corps n'est lu. AUCUN paramètre d'URL n'est accepté (pas même limit/cursor, ni dryRun) : tout paramètre → 400, contrôlé AVANT la clé d'API. Ne pas l'appeler pour simplement LIRE l'état du rejeu (GET /api/v1/webhook le rend).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Passe exécutée.
  • 400 — Un paramètre d'URL, quel qu'il soit : « limit »/« cursor » (message « Cette route ne pagine pas… »), « dryRun » ou tout autre (message « Filtre inconnu… »). Contrôlé avant la clé d'API.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 5.

E-mails

DELETE /api/v1/emails/{id}

Retire de la file un e-mail qui n'est JAMAIS PARTI (état ECHEC). Un e-mail ENVOYE ne se retire pas.

⭐ Sert à rendre une base propre avant une mise en service : un essai qui a engendré une notification vers une VRAIE adresse doit pouvoir être retiré AVANT que la messagerie du commerçant soit branchée. ⛔ Un e-mail à l'état ENVOYE est la preuve qu'un acheteur a été prévenu — cette preuve protège le commerçant et ne s'efface pas : 409. ⭐ Le retrait laisse une trace (destinataire, objet, genre, date) ; jamais le corps.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • id (path, obligatoire) — L'identifiant rendu par GET /api/v1/emails (champ « id » de « data »).
Codes de réponse
  • 200 — L'e-mail est retiré de la file.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Aucun e-mail ne porte cet identifiant DANS VOTRE journal. ⚠️ Un identifiant qui existe chez un autre client rend le même 404 : le message ne doit pas révéler son existence.
  • 409 — L'e-mail est PARTI (« status »: ENVOYE) : le journal prouve l'envoi et cette preuve ne s'efface pas. Champ « status » rendu.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.23.

GET /api/v1/emails

Le journal des e-mails transactionnels envoyés par le Hub pour l'organisation (200 plus récents au plus), sans leur contenu.

Lecture seule : QUI, QUOI, QUAND et SI C'EST PARTI — jamais le corps du message. Trié du plus récent au plus ancien.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • status (query) — Autre valeur : 422. ⚠️ « RETENU » n'apparaît QUE sur une boutique d'essai (bac à sable) : le message a été journalisé avec son corps et n'est parti chez PERSONNE. Ce n'est ni un succès ni un échec — ne le compte dans aucun des deux, et ne le renvoie jamais.
  • kind (query) — Égalité exacte sur le type d'envoi (ex. « RETRACTATION ») ; liste des valeurs non énumérée.
  • recipient (query) — Adresse du destinataire, égalité insensible à la casse.
  • since (query) — Envois créés à cette date ou après. Illisible : 422.
  • limit (query) — Accepté par le contrôle des paramètres (refuserFiltresInconnus l'admet d'office) mais NON LU par la route : aucun effet.
  • cursor (query) — Accepté par le contrôle des paramètres (refuserFiltresInconnus l'admet d'office) mais NON LU par la route : aucun effet. La liste n'est pas paginée (« nextCursor » vaut toujours null quand il est présent).
Codes de réponse
  • 200 — Les envois (200 au plus ; aucune pagination).
  • 400 — Paramètre d'URL inconnu : le message nomme le(s) paramètre(s) refusé(s) et liste ceux que la route accepte (« Filtre inconnu : … »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « status » hors de ENVOYE · ECHEC (details.status) ; « since » illisible (details.since).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.23.

Enrôlement

POST /api/v1/enroll

Branche un site en un seul appel : clé d'API, webhook, secret, Deploy Hook, dépôt média, capacités — authentifié par un jeton d'enrôlement à usage unique.

N'utilise PAS la clé d'API (aucun en-tête Authorization n'est lu). Ordre des contrôles : « ?dryRun » (400) → JSON (400) → schéma (422) → jeton absent (401) → jeton inconnu ou expiré (401, message identique) → jeton déjà utilisé (409) → clé d'API rattachée à une autre organisation (409) → Deploy Hook appelé et refusé définitivement (422, jeton intact) → une seule transaction (clé enregistrée ou réactivée, webhook, secret, Deploy Hook, média, capacités, jeton consommé) → 200. Si « deployHookUrl » est fourni, le Hub l'APPELLE réellement (POST, délai 8 s) avant d'enregistrer : 401/403/404/410 → refus 422 ; 5xx, réseau ou délai → enregistré quand même, signalé dans « deployHookCheck ». Secret de webhook : celui du corps, sinon celui déjà enregistré pour l'organisation, sinon un nouveau (« whsec_ » + 64 hexadécimaux) renvoyé une seule fois. Mode média : « CLIENT » si « mediaUploadUrl » est fourni, sinon « LOCAL » dans la réponse (la base n'est alors pas modifiée sur ce point). Rejouer avec le même jeton → 409 ; modifier ensuite : POST /api/v1/webhook avec la clé d'API. Paramètres d'URL autres que « dryRun » : ignorés.

Authentification
Authentification particulière, décrite dans le texte de l'opération (jeton à usage unique, lien signé).
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON DISPONIBLE sur cette route : sa seule présence (quelle que soit sa valeur) est refusée en 400 par « refuserDryRunNonSupporte », avant tout traitement. Ne pas l'envoyer.
  • X-Enrollment-Token (header) — Jeton d'enrôlement. Prioritaire sur « enrollmentToken » du corps. L'un des deux est requis.
Codes de réponse
  • 200 — Site enrôlé ; jeton consommé.
  • 400 — « ?dryRun » présent dans l'URL, OU corps qui n'est pas un JSON valide.
  • 401 — Jeton d'enrôlement absent (« Jeton d'enrôlement requis (en-tête « X-Enrollment-Token » ou champ « enrollmentToken »). »), OU inconnu / expiré (« Jeton d'enrôlement invalide ou expiré. », même message pour les deux).
  • 409 — Jeton déjà utilisé (le site est branché : utiliser POST /api/v1/webhook avec la clé d'API), OU clé d'API fournie déjà rattachée à une autre organisation.
  • 422 — Deux formes. (a) Schéma invalide : { error: « Les données d'enrôlement sont invalides. », details } (champs fautifs, « _demande » pour le niveau racine). (b) Deploy Hook appelé et refusé définitivement (401/403/404/410) : forme PROPRE, voir schéma — rien n'est enregistré et le jeton reste valable.
Règle d'usage
Master prompt, § 1.

État

GET /api/v1/etat

L'état de fermeture et la version de l'organisation, en quelques octets (aucun cache).

Comparer « version » à la dernière valeur gardée : identique, rien à relire ; différente, relire GET /api/v1/webhook une fois. Quand la boutique est fermée, « maintenance » porte aussi l'habillage et la consigne technique. En-tête « Cache-Control: no-store ».

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — L'état.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Organisation illisible (introuvable en base) : ne rien conclure, garder la dernière valeur connue (« Organisation illisible. Ne conclus RIEN de cette réponse… »). Corps { error } sans details. — OU, commun à toutes les routes à clé : Données de l'organisation en cours de reconstruction : ce N'EST PAS une absence de données. Ne rien synchroniser ni supprimer ; réessayer après « Retry-After ».
Règle d'usage
Master prompt, § 4.14.
Réserves
  • Volontairement non détaillé : maintenance.exempt.note, buyerFacing, adminPathUnknown, webhook : phrases fixes en français, non recopiées.

Factures

GET /api/v1/invoices

Les factures et avoirs émis par l'organisation à ses acheteurs (200 plus récents au plus, par date d'émission décroissante).

Uniquement les documents de VENTE de l'organisation (jamais les factures que l'éditeur lui adresse : voir le journal « factures-editeur »). « count » compte les lignes, « countByKind » les ventile. Liste non paginée.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • customerEmail (query) — E-mail de l'acheteur de la commande liée, insensible à la casse.
  • orderNumber (query) — Numéro de la commande liée (égalité exacte).
  • orderExternalRef (query) — Référence externe de la commande liée (égalité exacte).
  • kind (query) — Autre valeur : 400.
  • since (query) — Documents MODIFIÉS (« updatedAt ») à cette date ou après. Illisible : 400.
  • limit (query) — Accepté par le contrôle des paramètres (refuserFiltresInconnus l'admet d'office) mais NON LU par la route : aucun effet.
  • cursor (query) — Accepté par le contrôle des paramètres (refuserFiltresInconnus l'admet d'office) mais NON LU par la route : aucun effet. La liste n'est pas paginée (« nextCursor » vaut toujours null quand il est présent).
Codes de réponse
  • 200 — Les documents.
  • 400 — Paramètre d'URL inconnu (« Filtre inconnu : … ») ; « kind » hors de INVOICE · CREDIT_NOTE ; « since » illisible.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.10.
Réserves
  • Non établi depuis le code : Signe des montants d'un avoir (négatifs dans l'exemple) : non établi depuis cette route, qui rend les colonnes telles qu'enregistrées.

GET /api/v1/invoices/{number}/facturx

Rend le XML Factur-X (profil EN 16931) d'une facture ou d'un avoir, par son numéro.

Deux preuves cumulées : la clé d'API (Authorization: Bearer) prouve le site ; le jeton du chemin prouve l'acheteur. La commande est cherchée par { pickupRescheduleToken: jeton, organizationId: organisation de la clé }. Le XML est DÉJÀ dans le PDF — c'est le principe de Factur-X. On l'expose à part pour le comptable dont l'outil sait lire un XML mais pas l'extraire d'un PDF, et pour vérifier ce qui a RÉELLEMENT été émis : c'est le XML, pas le PDF, que lit la comptabilité du destinataire. ⚠️ Il est construit depuis l'INSTANTANÉ de la facture, jamais depuis les données du jour : si l'adresse du vendeur change demain, ce XML continue de porter celle qui a été facturée.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • number (path, obligatoire) — Le numéro du document, tel qu'il est imprimé (ex. « FA-2026-0001 »).
Codes de réponse
  • 200 — Le XML Factur-X, en pièce jointe.
  • 400 — Paramètre de requête inconnu (contrôlé avant la clé).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Aucun document de cette boutique ne porte ce numéro. ⚠️ 404 et non 403 : un 403 confirmerait l'existence du document d'une autre boutique.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 6.4.

GET /api/v1/invoices/{number}/pdf

Le PDF d'une facture ou d'un avoir de l'organisation, servi directement (pas de redirection).

Rendu à partir de l'instantané figé du document (Factur-X), avec le logo figé s'il est joignable. À relayer tel quel à l'acheteur, sans le régénérer.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • number (path, obligatoire) — Numéro du document (ex. « F-2026-00012 »), URL-encodé ; cherché dans l'organisation de la clé.
Codes de réponse
  • 200 — Le fichier PDF.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Aucun document de ce numéro dans l'organisation (« Document introuvable. »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.10.

POST /api/v1/invoices/{number}/deposit

Dépose une facture (ou un avoir) sur la plateforme de facturation électronique du commerçant.

Deux preuves cumulées : la clé d'API (Authorization: Bearer) prouve le site ; le jeton du chemin prouve l'acheteur. La commande est cherchée par { pickupRescheduleToken: jeton, organizationId: organisation de la clé }. C'est le geste MANUEL : il passe outre le réglage « pdpDepotAutomatique » mis en pause, parce qu'une demande explicite ne doit pas être ignorée par un interrupteur que le commerçant a lui-même posé. ⚠️⚠️ UN DÉPÔT NE SE RETIRE PAS : il entre dans une chaîne déclarative et il est transmis au portail public. Il n'y a donc AUCUN dépôt en lot. ⚠️ « SENT » veut dire DÉPOSÉE, pas ACCEPTÉE — la plateforme rend sa décision ensuite ; relire « pdpStatus » par GET /api/v1/invoices. Une facture déjà acceptée n'est jamais redéposée (un doublon dans un flux fiscal se règle par courrier).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • number (path, obligatoire) — Le numéro du document, tel qu'il est imprimé (ex. « FA-2026-0001 »).
Codes de réponse
  • 200 — Le dépôt a été tenté ; l'état rendu est RELU EN BASE, pas déduit.
  • 400 — Paramètre de requête inconnu (contrôlé avant la clé).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Aucune facture de cette boutique ne porte ce numéro.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 502 — La plateforme est injoignable. La facture reste prête à déposer — rien n'est perdu.
Règle d'usage
Master prompt, § 6.4.

Journal des défauts

GET /api/v1/defauts

Tes propres dépôts, et ce que nous en avons fait.

⭐ AVEC TA CLÉ : nous rendons les 100 derniers dépôts de TON organisation, avec leur état et le correctif que nous y avons écrit. C'est ainsi que tu apprends ce qu'un défaut signalé est devenu — notre contrat « 2026-09-18.12 » te demande de le lire à chaque session. ⛔ SANS CLÉ : 401. Le dépôt, lui, reste anonyme (POST sans clé accepté) — mais la LECTURE ne l'est pas : un dépôt porte le témoin d'un autre commerçant, ses adresses, ses comptes. Ce journal n'est pas une place publique, et le cloisonnement vaut ici comme partout. ⚠️ AUCUN FILTRE N'EST ACCEPTÉ : « ?since= » ou tout autre paramètre est refusé en 400, jamais avalé en silence.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Tes 100 derniers dépôts, du plus récent au plus ancien. Cette route NE PAGINE PAS : « limit » et « cursor » sont acceptés et sans effet, et la réponse le dit (« warningPagination »).
  • 400 — Filtre inconnu. Le message NOMME le paramètre refusé et liste ceux qui sont acceptés (« limit », « cursor »).
  • 401 — Clé d'API requise. ⚠️ Le DÉPÔT (POST) reste anonyme ; la LECTURE, non — un dépôt porte le témoin d'un autre commerçant. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 10.1.

POST /api/v1/defauts

Dépose un défaut CONSTATÉ ET MESURÉ dans le Hub ou dans ses textes. Aucune clé n'est nécessaire.

⭐ AUCUNE AUTHENTIFICATION REQUISE : un agent qui n'a jamais câblé ce site peut déposer. Une clé d'API valide, si elle est jointe, range le dépôt dans le bac « SIGNE » (on sait d'où il vient) ; sans clé — ou avec une clé invalide — il tombe dans le bac « ANONYME » et reste lu. ⚠️ « temoin » EST OBLIGATOIRE : il dit ce qui a été MESURÉ (attendu, reçu, comment). Un dépôt sans témoin est refusé en 400 « temoin_requis » — une impression n'est pas un signalement. ⛔ CE N'EST PAS UNE BOÎTE À QUESTIONS : « /sitemap.xml renvoie 404 alors que le § 4.24 annonce 200 » se dépose ; « dois-je vraiment câbler les avis ? » ne se dépose pas — cela se demande à SON PROPRE gérant. ⚠️ SI UN CANAL AGENT-À-AGENT EST OUVERT, ne pas utiliser ce journal : le dire dans le canal, où quelqu'un répond. ⚠️ AUCUNE RÉPONSE INDIVIDUELLE N'EST GARANTIE, et aucune sur un dépôt anonyme. Corps limité à 64 Kio ; au-delà, 413. Limite de débit par heure et par origine ; au-delà, 429.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — ⭐ HONORÉ : tout est vérifié (titre, témoin, tailles, limite de débit) et RIEN n'est écrit. La réponse porte « aBlanc: true » et l'empreinte de regroupement qu'aurait eue le dépôt.
Codes de réponse
  • 201 — Dépôt enregistré : « recu », « id », « bac », « suite ».
  • 400 — « json_invalide », ou « temoin_requis » quand « titre » ou « temoin » manque ou est vide.
  • 405 — GET : ce journal se dépose, il ne se lit pas.
  • 413 — « corps_trop_grand » : au-delà de 64 Kio.
  • 429 — « trop_de_depots » : limite horaire atteinte.
Règle d'usage
Master prompt, § 10.1.

Journaux

GET /api/v1/journaux

L'index des journaux lisibles : pour chacun, son contenu, ses colonnes, sa colonne de tri et ses filtres.

N'accepte AUCUN paramètre d'URL, pas même « limit » ni « cursor ». Contrôle des paramètres AVANT celui de la clé. ⚠️ « filtres » ne mentionne pas « grouper », pourtant accepté par /journaux/{nom}.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Les 25 journaux, triés par nom.
  • 400 — Paramètre d'URL refusé : « limit » ou « cursor » (« Cette route ne pagine pas : elle rend toujours la liste complète… »), ou tout autre paramètre (« Filtre inconnu : … »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Réserves
  • Volontairement non détaillé : data[].quoi : remplacé par « … » dans l'exemple (le Hub rend une phrase par journal).

GET /api/v1/journaux/{nom}

Lit un journal de l'organisation, du plus récent au plus ancien, paginé par « before ».

Rend au plus « limit » lignes (défaut 100) triées par la colonne de date du journal (« orderedBy »), décroissant. S'il en reste, « hasMore » vaut true et « nextBefore » porte la date de la dernière ligne : la renvoyer en « before ». Avec « grouper », « data » est vide et « groupes » regroupe la page par message (colonne « message », sinon la 2ᵉ colonne du journal). Nom inconnu : 404 AVANT tout contrôle de clé.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • nom (path, obligatoire) — Nom du journal (index : GET /api/v1/journaux).
  • since (query) — Lignes dont la date (« orderedBy ») est ≥ cette date. Illisible : 422.
  • before (query) — Lignes dont la date est < cette date (pagination : valeur de « nextBefore »). Illisible : 422.
  • limit (query) — Taille de page. Hors bornes ou non entier : 422.
  • grouper (query) — « 1 »/« true » : rend « groupes » au lieu des lignes. Autre valeur : 422.
  • direction (query) — ⭐⭐ SUR « /journaux/synchros » UNIQUEMENT, et c'est le paramètre qui évite le contresens : ce journal porte DEUX SENS dans la même liste. « sortant » = ce que NOUS t'envoyons (chaque ligne porte alors « payload.evenementId ») ; « entrant » = ce que TON site nous envoie (pas d'evenementId, et c'est normal). ⚠️ Toute autre valeur est REFUSÉE en 422 et NOMME les deux acceptées — jamais une liste vide, qui se lirait « rien à signaler ». ⭐ Entrée de contrat « 2026-09-22.02 ».
  • cursor (query) — Accepté par le contrôle des paramètres (refuserFiltresInconnus l'admet d'office) mais NON LU par la route : aucun effet. La liste n'est pas paginée (« nextCursor » vaut toujours null quand il est présent).
  • kind (query) — Filtre d'égalité propre à : barrieres-age, factures-editeur, services-branches (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • status (query) — Filtre d'égalité propre à : campagnes, devis-editeur, emails-campagnes, invitations, messages-editeur, rendez-vous, synchros (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • action (query) — Filtre d'égalité propre à : cgvu (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • version (query) — Filtre d'égalité propre à : cgvu (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • periodKey (query) — Filtre d'égalité propre à : e-reporting (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • recipientEmail (query) — Filtre d'égalité propre à : emails-campagnes, scenarios-envois (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • role (query) — Filtre d'égalité propre à : equipe (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • email (query) — Filtre d'égalité propre à : equipe, invitations (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • level (query) — Filtre d'égalité propre à : erreurs (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • source (query) — Filtre d'égalité propre à : erreurs, synchros (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • paymentStatus (query) — Filtre d'égalité propre à : factures-editeur (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • reason (query) — Filtre d'égalité propre à : rendez-vous (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • scenarioKey (query) — Filtre d'égalité propre à : scenarios, scenarios-envois (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • provider (query) — Filtre d'égalité propre à : services-branches (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • operation (query) — Filtre d'égalité propre à : synchros (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • code (query) — Filtre d'égalité propre à : transporteurs (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • active (query) — Filtre d'égalité propre à : transporteurs (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
  • path (query) — Filtre d'égalité propre à : visites (sur tout autre journal : 400). « true »/« false » sont convertis en booléens ; toute autre valeur est comparée telle quelle. Une valeur que la base ne sait pas comparer (ex. valeur hors énumération) rend 500.
Codes de réponse
  • 200 — Une page du journal.
  • 400 — Paramètre d'URL inconnu : le message nomme le(s) paramètre(s) refusé(s) et liste ceux que la route accepte (« Filtre inconnu : … »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « {nom} » n'est pas un journal du Hub ; « details.journaux » liste les noms valides, « details.indice » renvoie à l'index.
  • 422 — « since » ou « before » illisible (details.<paramètre>) ; « limit » hors de 1 à 500 ; « grouper » d'une autre valeur que 1 · true · 0 · false.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 500 — La lecture en base a échoué (ex. valeur de filtre incompatible avec la colonne) : « Ce journal n'a pas pu être lu. Réessaie ; si ça persiste, dis-le-nous par le canal. »
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Réserves
  • Volontairement non détaillé : data[] : colonnes propres à chaque journal (liste dans GET /api/v1/journaux, champ « champs ») ; leurs types et valeurs ne sont pas décrits ici.
  • Volontairement non détaillé : Valeurs admises des filtres propres (kind, status, level, source, role…) : non énumérées ; ce sont des égalités sur la colonne du même nom.

Lettre d'information

DELETE /api/v1/newsletter

Désinscrit une adresse (le contact est conservé, subscribed=false).

Répond 200 même pour une adresse inconnue (aucune énumération possible). Les autres paramètres d'URL sont ignorés, « dryRun » compris.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • email (query, obligatoire) — Adresse à désinscrire (espaces retirés, comparée en minuscules).
Codes de réponse
  • 200 — Désinscrit (ou adresse inconnue).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « Le paramètre « email » est requis et doit être valide. » (sans « details »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.9 bis.

POST /api/v1/newsletter

Inscrit une adresse à la lettre d'information (consentement), renvoie le code de bienvenue éventuel.

Upsert du contact avec subscribed=true (création : source « newsletter » si absente ; mise à jour : name et tags seulement s'ils sont fournis — « source » n'est écrite qu'à la création). Réponse identique qu'on soit déjà inscrit ou non : une adresse déjà inscrite reçoit le MÊME code, et l'e-mail de bienvenue ne repart pas. « ?dryRun » est refusé en 400 avant la vérification de la clé.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON DISPONIBLE sur « POST /api/v1/newsletter » : sa seule présence (quelle que soit la valeur) est refusée en 400 par refuserDryRunNonSupporte, AVANT la vérification de la clé. Ne l'envoie pas.
Codes de réponse
  • 201 — Inscrit.
  • 400 — Corps non JSON (« Le corps de la requête doit être un objet JSON. »), ou « ?dryRun » présent.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « Les données d'inscription sont invalides. » (details = flatten().fieldErrors).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.9 bis.
Réserves
  • Volontairement non détaillé : Forme exacte de « discountCode » (code partagé ou nominatif engendré par le scénario « inscription-newsletter »).

Livraison

GET /api/v1/shipping/forfait

Lit les tranches de poids du forfait boutique, le franco et le mode de livraison en cours.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Le forfait (tranches triées par minWeightGrams croissant).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.14.
Réserves
  • Non établi depuis le code : Liste exhaustive des valeurs de shippingMode (Organization.modeDeLivraison est une chaîne libre ; CONFIGURE et FORFAIT sont celles lues dans le code).

GET /api/v1/shipping/grille

Lit la grille de poids d'UNE zone d'un transporteur.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • carrier (query, obligatoire) — CODE du transporteur (égalité exacte, espaces de bord retirés).
  • zone (query, obligatoire) — LIBELLÉ de la zone (égalité exacte, espaces de bord retirés).
  • limit (query) — Accepté d'office par refuserFiltresInconnus, mais non lu : la grille est toujours rendue entière.
  • cursor (query) — Accepté d'office, non lu.
Codes de réponse
  • 200 — La grille (tranches triées par minWeightGrams croissant).
  • 400 — Paramètre de requête inconnu (contrôlé APRÈS la clé).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Aucune zone de ce libellé chez ce transporteur de l'organisation (créer d'abord par POST /api/v1/config/zones-livraison).
  • 422 — « carrier » ou « zone » absent ou vide.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.14.

GET /api/v1/shipping/service-points

Cherche les points relais autour d'un code postal, ou relit UN point par son identifiant (via la clé SendCloud confiée au Hub).

Deux modes : avec « id » → relecture d'un point, réponse { object: "service_point", data } ; sans « id » → recherche, réponse { object: "list", data, count } (au plus 20 points, pas de pagination, pas l'enveloppe listResponse). « carrier » filtre côté Hub en comparant lettres et chiffres dans les deux sens (un code de fiche Hub contenant le nom fonctionne) ; si le filtre ne garde rien alors que des points existent → 422 carrier_unknown listant les transporteurs disponibles. Nécessite une connexion SENDCLOUD active, gardée par le Hub (custody HUB).

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • country (query, obligatoire) — Code pays ISO à 2 lettres (espaces retirés, mis en majuscules).
  • id (query) — Identifiant d'un point à relire. S'il est fourni, postalCode, city et carrier sont ignorés.
  • postalCode (query) — OBLIGATOIRE quand « id » est absent (recherche).
  • city (query) — Facultatif (recherche).
  • carrier (query) — Facultatif : NOM de transporteur (ex. « mondial_relay »), filtré côté Hub.
Codes de réponse
  • 200 — Liste de points (recherche) ou un point (relecture par id).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — service_point_not_found : relecture par id sans résultat — le Hub ne distingue pas un id inconnu d'un transporteur injoignable (réessayer).
  • 409 — carrier_not_connected : aucune clé SendCloud confiée au Hub ; carrier_key_unreadable : clé enregistrée illisible.
  • 422 — invalid_country : country absent ou pas 2 caractères ; invalid_postal_code : ni id ni postalCode ; carrier_unknown : aucun point pour ce transporteur alors que d'autres existent ; carrier_no_service_points (06/10/2026) : transporteur reconnu, mais le compte du prestataire ne fournit AUCUN point relais pour lui à cette adresse, même sur demande explicite.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 502 — carrier_unavailable : le transporteur n'a pas répondu à la recherche (ne veut PAS dire « aucun relais »).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 5.
Réserves
  • Non établi depuis le code : Valeurs possibles de data[].type : general_shop_type de SendCloud, rendu sans interprétation.
  • Volontairement non détaillé : data[].horaires : structure SendCloud rendue telle quelle, non décrite.

POST /api/v1/shipping/quote

Calcule les frais de port d'un panier : une ligne par mode de livraison, avec la provenance du prix.

Calcul sans écriture. Contrôle d'abord la destination (même verdict que la création de commande). Boutique au FORFAIT : aucun transporteur ni prestataire, une ligne « FORFAIT » + les lignes RETRAIT éventuelles (productExternalRefs n'y est pas appliqué). Sinon : une ligne par transporteur actif (filtré par carrierCode), le prestataire interrogé UNE fois si la clé est confiée au Hub, plus les transporteurs actifs chez le prestataire mais non déclarés (seulement sans carrierCode), puis filtrage par les restrictions des produits du panier (intersection). ⚠⚠ CETTE ROUTE RÉPOND PENDANT LE TUNNEL DE PAIEMENT : « message » s'adresse à toi, l'intégrateur ; « messagePourAcheteur » est prêt à afficher tel quel à un consommateur. N'affiche JAMAIS « message » ni « error » à un acheteur. Tous les refus ci-dessous portent « messagePourAcheteur » à la racine, SAUF les formes (1) et (2) où il n'y a rien à expliquer à l'acheteur. Le corps 200 prend l'une de ces formes : (1) cas ordinaire { quotes, filtered?, notice } ; (2) forfait { quotes, shippingMode:"FORFAIT", message } ; (2 bis) forfait dont les articles n'acceptent pas l'expédition, retrait encore ouvert { quotes (RETRAIT seuls), shippingMode:"FORFAIT", messagePourAcheteur, message } ; (2 ter) forfait, plus rien de possible { quotes: [], shippingMode:"FORFAIT", reason:"aucun_mode_commun", messagePourAcheteur, message } ; (3) forfait, poids hors tranches { quotes (ligne FORFAIT à priceCents null, qui porte elle aussi son messagePourAcheteur), shippingMode:"FORFAIT", reason:"poids_hors_forfait", messagePourAcheteur, message } ; (4) aucun transporteur actif et pas de clé confiée { quotes: [], reason:"aucun_transporteur", messagePourAcheteur, message } ; (5) articles incompatibles { quotes: [], reason:"aucun_mode_commun", messagePourAcheteur, message } ; pays qui n'est pas un code ISO connu → 400 details.code "country_unknown" (11/09/2026). ⚠️ Un carrierCode inconnu, sans clé confiée au Hub, tombe dans la forme (4). ⭐ DEVIS PAR COLIS (06/10/2026, contrat 2026-10-06.02) : avec « items », si la boutique a au moins un emballage avec « capacitePieces » ou « maxContentGrams », le Hub lit CES produits dans la boutique de la clé (poids et pièces de la variante, sinon du produit), les répartit en colis (un plein, puis un plus petit), tarife CHAQUE colis par le même calcul que le devis à un poids et ADDITIONNE : « priceCents » de chaque ligne d'expédition = somme de ses « parcels[] », 0 si le franco est atteint quel que soit le nombre de colis. La racine porte alors « packing » { mode: PAR_COLIS | POIDS_UNIQUE | IMPOSSIBLE, reason, parcelCount, message }. Produit ou variante introuvable dans CETTE boutique → 422 details.code « produit_introuvable » | « variante_introuvable ». Colisage impossible → lignes d'expédition à priceCents null (unavailableReason « COLISAGE_IMPOSSIBLE »), retrait gardé, reason « colisage_impossible » au forfait. Sans « items » : réponse STRICTEMENT inchangée, sauf « parcels: [] » sur chaque ligne.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Devis (voir les formes énumérées dans la description de l'opération).
  • 400 — Corps JSON illisible (« Corps JSON illisible. »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Organisation introuvable.
  • 422 — Trois cas, trois formes de « details » : (a) corps invalide → details = error.flatten() de Zod { formErrors: string[], fieldErrors: { <champ>: string[] } } ; (b) pays refusé → details = { code: "country_not_served" (réglage que le gérant peut ouvrir) | "country_forbidden" (interdiction légale), deblocable: boolean } ; (c) 06/10/2026, avec « items » : un article introuvable DANS CETTE BOUTIQUE → details = { code: "produit_introuvable" | "variante_introuvable", ref } (aucun prix deviné). ⚠ Les cas (b) et (c) portent « messagePourAcheteur » ; le corps invalide est un défaut de TON appel, que ton acheteur n'a pas pu provoquer. « message » s'adresse à toi, l'intégrateur ; « messagePourAcheteur » est prêt à afficher tel quel à un consommateur. N'affiche JAMAIS « message » ni « error » à un acheteur.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.14.
Réserves
  • Non établi depuis le code : Exhaustivité de quotes[].unavailableReason : valeurs relevées en recherchant les affectations de « motif », sans lecture intégrale de devisExpedition.
  • Non établi depuis le code : Exemple : la branche « grille du Hub, clé gardée par le site » (fallbackReason PAS_DE_GARDE_HUB, fallback false) est déduite de phraseRepli et de la fonction de repli, sans lecture ligne à ligne de tout devisExpedition.
  • Volontairement non détaillé : quotes[].message : textes produits par devisExpedition / phraseRepli (src/lib/shipping-quote.ts) selon la branche, non recopiés un par un.

PUT /api/v1/shipping/forfait

Remplace ENTIÈREMENT la grille du forfait boutique (et le franco). Refuse en bloc une grille trouée.

Remplacement atomique (transaction) : rejouable sans doublon. N'ACTIVE PAS le forfait (voir PATCH /api/v1/settings, « modeDeLivraison »). ⚠️ « freeShippingFromCents » absent ou null EFFACE le franco existant.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — Non supporté : sa seule présence (quelle que soit sa valeur) rend 400, AVANT même le contrôle de la clé (refuserDryRunNonSupporte, message citant « POST /api/v1/shipping/forfait »).
Codes de réponse
  • 200 — Grille enregistrée. « tiers » est rendu dans l'ordre d'ENVOI, chaque tranche avec son libellé.
  • 400 — « ?dryRun » présent (avant la clé) ; corps JSON illisible ; corps qui n'est pas un objet JSON.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « tiers » absent ou non tableau (details.tiers) ; clé inconnue (details.unknownFields) ; tranche non objet ou bornes/prix hors type ou hors bornes (« Ligne N : … », sans details) ; grille incohérente (details.defects, TOUS les défauts) ; freeShippingFromCents non entier ou < 1.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.14.

PUT /api/v1/shipping/grille

Remplace ENTIÈREMENT la grille de poids d'une zone. Refuse en bloc une grille trouée.

Remplacement atomique (transaction) : rejouable sans doublon. Ordre des contrôles : dryRun (400, avant la clé) → JSON (400) → clés inconnues (422) → carrier/zone (422) → zone existante (404) → tiers (422) → cohérence de la grille (422).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — Non supporté : sa seule présence (quelle que soit sa valeur) rend 400, AVANT même le contrôle de la clé (refuserDryRunNonSupporte, message citant « POST /api/v1/shipping/grille »).
Codes de réponse
  • 200 — Grille enregistrée. « tiers » dans l'ordre d'ENVOI. (Pas de carrierLabel ni de rules ici.)
  • 400 — « ?dryRun » présent (avant la clé) ; corps JSON illisible ; corps qui n'est pas un objet JSON.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Aucune zone de ce libellé chez ce transporteur de l'organisation (contrôlé AVANT les tranches).
  • 422 — Clé inconnue (details.unknownFields) ; carrier/zone manquant ; « tiers » absent ou non tableau ; tranche invalide (« Ligne N : … ») ; grille incohérente (details.defects).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.14.

Marques

DELETE /api/v1/brands/{identifier}

Supprime DÉFINITIVEMENT une marque sans produit ; refuse en 409 si des produits y sont rattachés.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — Marque désignée par son « id » Hub, son « externalRef » OU son « slug » (le premier enregistrement trouvé de l'organisation parmi les trois).
Codes de réponse
  • 200 — Marque supprimée.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Marque introuvable. »
  • 409 — Des produits y sont encore rattachés. Forme PROPRE (pas « Erreur ») : « error » est un code.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.2.

GET /api/v1/brands

Liste TOUTES les marques (actives par défaut) ; ne pagine pas et refuse « limit » / « cursor ».

Tri : « position » croissante puis « name ». « limit » et « cursor » sont REFUSÉS en 400 (« Cette route ne pagine pas »). L'appel à « unknownParameterWarnings » ne peut rien produire : tout paramètre autre que « isActive » est refusé.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • isActive (query) — Visibilité. Absent = « true » (seulement ce qui est en ligne). « false » = seulement ce qui est masqué, « all » = tout. Casse et espaces de bord ignorés. Toute autre valeur → 422 « invalid_filter_value ».
Codes de réponse
  • 200 — Enveloppe de liste, « nextCursor » toujours null.
  • 400 — Paramètre autre que « isActive » ; « limit » ou « cursor » (message : « Cette route ne pagine pas »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Valeur de « isActive » autre que true / false / all (code « invalid_filter_value »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.2.
Réserves
  • Volontairement non détaillé : « emptyReason » est calculé SANS tenir compte du filtre « isActive » (même remarque que les catégories).

GET /api/v1/brands/{identifier}

Rend une marque (active ou masquée) et son nombre de produits rattachés.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — Marque désignée par son « id » Hub, son « externalRef » OU son « slug » (le premier enregistrement trouvé de l'organisation parmi les trois).
Codes de réponse
  • 200 — La marque.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Marque introuvable. »
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.2.

POST /api/v1/brands

Synchronise un lot de marques (site → Hub), idempotent par « externalRef ».

Corps : tableau brut ou { "brands": [...] }, 1 à 1000. Une marque du Hub sans référence et portant le même slug est ADOPTÉE (comptée « updated ») ; un slug déjà pris par une marque portant une autre référence fait échouer la ligne. Mise à jour = fusion (« name » et « slug » écrits, le reste seulement s'il est présent). Aucun « ignoredFields » sur cette route : une clé inconnue est ignorée en silence. La synchronisation ne supprime rien. Paramètres d'URL autres que « dryRun » : non contrôlés.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON disponible sur « POST /api/v1/brands » : sa simple présence, quelle que soit sa valeur (même vide), est refusée en 400 — AVANT le contrôle de la clé. L'essai à blanc n'existe que sur « POST /api/v1/products ».
  • deploy (query) — « 1 » (exactement) : demande la reconstruction du site après l'écriture, si aucune ligne n'a échoué. Toute autre valeur est sans effet et n'ajoute aucune clé « deploy » à la réponse.
Codes de réponse
  • 200 — Aucune ligne en échec.
  • 207 — Au moins une ligne a échoué (« failed » > 0). Même forme ; les lignes réussies sont écrites.
  • 400 — « dryRun » présent dans l'URL (contrôlé AVANT la clé), ou corps qui n'est pas du JSON valide.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Corps invalide (« Les données de marques sont invalides. ») ; « details » = « flatten().fieldErrors », clés de premier niveau (en pratique « brands »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.2.

Médias

GET /api/v1/media

Liste les adresses de médias RÉFÉRENCÉES par le Hub pour l'organisation (à conserver), en entier, sans pagination.

Sources : image principale, galerie, textes, documents et variations des produits ; image et texte des catégories ; logo et texte des marques ; couverture et textes des articles ; logo de l'organisation ; logo figé dans les instantanés de factures. Adresses de texte : valeurs des attributs « src » et « href » (sauf « # », « mailto: », « tel: ») — elles peuvent donc être relatives. Triée par adresse, sans doublon. Aucun paramètre accepté : tout paramètre (y compris « limit », « cursor ») → 400. Pas d'indicateur de liste vide.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Inventaire complet.
  • 400 — Tout paramètre d'URL (« limit » / « cursor » : « Cette route ne pagine pas » ; autre : « Filtre inconnu »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 7.3.

Messages de contact

DELETE /api/v1/messages/{id}

Supprime un message de contact reçu.

⛔ LA SUPPRESSION LAISSE UNE LIGNE — qui, quand, quel expéditeur, quel objet, dans quel état était le message — mais JAMAIS LE CORPS : le garder reviendrait à ne rien supprimer du tout. ⭐ Cette ligne existe parce qu'un message de contact peut être une DEMANDE DE RÉTRACTATION ou une réclamation, c'est-à-dire un écrit qui engage : une suppression sans trace permettrait de le faire disparaître. ⭐ Et le raisonnement qui pousse dans l'autre sens, pour que tu l'aies aussi : le RGPD demande de NE PAS conserver indéfiniment les données d'un tiers. Garder à vie le nom et l'adresse de quelqu'un qui a écrit une fois est un problème en soi — c'est pourquoi nous supprimons vraiment, au lieu d'archiver sans fin. ⚠️ UN MESSAGE DÉJÀ SUPPRIMÉ REND LE MÊME 404 QU'UN IDENTIFIANT INVENTÉ : nous ne disons pas ce qui a existé. Ne t'en sers pas pour deviner l'historique d'une boutique.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • id (path, obligatoire) — L'identifiant rendu par « GET /api/v1/messages ».
Codes de réponse
  • 200 — Le message est supprimé, la ligne est conservée.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « message_introuvable » — inconnu, ou déjà supprimé : les deux se ressemblent, et c'est voulu.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 10.8.

GET /api/v1/messages

Liste les messages de contact reçus (enveloppe de liste, pagination par curseur).

Tri par createdAt décroissant ; « since » porte sur createdAt (et non updatedAt).

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • since (query) — Filtre delta : ne rend que les éléments dont la date de référence est postérieure ou égale. Toute valeur que `new Date()` ne sait pas lire est refusée en 400 « Le filtre « since » doit être une date ISO valide. ».
  • limit (query) — Taille de page. Hors de 1..200 ou non entier : 400.
  • cursor (query) — Curseur de pagination : l'« id » du dernier élément de la page précédente (valeur de « nextCursor »).
Codes de réponse
  • 200 — Liste des messages.
  • 400 — Paramètre inconnu, « since » illisible, « limit » hors 1..200.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.9.

PATCH /api/v1/messages/{id}

Pose le statut d'un message de contact (lu, répondu, archivé, indésirable).

⭐ 30/09/2026 (2026-09-30.09). Même règle que l'écran du commerçant : « REPLIED » horodate la réponse une seule fois (« repliedAt »). Seul « status » se modifie — tout autre champ → 422 (le texte d'un message est l'écrit de son auteur). Aucun webhook « message.updated » n'est renvoyé au site pour ce geste.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • id (path, obligatoire) — L'identifiant rendu par « GET /api/v1/messages ».
Codes de réponse
  • 200 — Statut posé.
  • 400 — Corps non JSON ou non objet.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « message_introuvable » — inconnu, déjà supprimé, ou d'une autre boutique.
  • 422 — « Statut de message refusé : rien n'a été modifié. » — valeur hors liste, ou champ autre que « status ».
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 10.8.

POST /api/v1/messages

Enregistre un message du formulaire de contact du site (statut NEW).

Deux e-mails best-effort, par la configuration d'envoi de l'organisation : accusé de réception à l'expéditeur, alerte au gérant (adresse de l'organisation, à défaut celle du propriétaire). Leur échec n'affecte pas la réponse. L'IP (x-forwarded-for) et l'user-agent sont conservés. « ?dryRun » est refusé en 400 avant la vérification de la clé.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON DISPONIBLE sur « POST /api/v1/messages » : sa seule présence (quelle que soit la valeur) est refusée en 400 par refuserDryRunNonSupporte, AVANT la vérification de la clé. Ne l'envoie pas.
Codes de réponse
  • 201 — Message enregistré.
  • 400 — Corps non JSON, ou « ?dryRun » présent.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « Les données du message sont invalides. » (details = flatten().fieldErrors).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.9.

Mise en service

GET /api/v1/boutique-d-essai

As-tu déjà une boutique d'essai, et que contient-elle ?

Rend l'état de la boutique d'essai jumelle de TON organisation (celle de ta clé). ⭐ « AUCUN » veut dire qu'elle n'existe pas encore : un POST la crée. ⚠️ Aucun paramètre n'est accepté : tout filtre est refusé en 400.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — L'état, dans l'une de ses trois formes.
  • 400 — Un paramètre a été envoyé : cette route n'en accepte aucun.
  • 401 — Clé d'API requise. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 2.6.

POST /api/v1/boutique-d-essai

Crée ta boutique d'essai et rends-moi sa clé — sans déranger le commerçant.

⭐⭐ C'EST LA ROUTE QUI TE REND AUTONOME. Elle crée la boutique d'essai jumelle de ton organisation si elle n'existe pas, puis elle te rend UNE CLÉ D'API neuve qui n'ouvre QUE cet essai. ⚠️ AUCUN CORPS N'EST REQUIS et aucun n'est lu : l'organisation vient de ta clé, jamais d'un paramètre. ⭐ IDEMPOTENTE SUR LA BOUTIQUE, PAS SUR LA CLÉ : rappelée, elle ne recrée pas l'essai (« created »: false) mais elle rend une NOUVELLE clé — c'est ainsi qu'on se dépanne d'une clé perdue, et les anciennes restent valables. ⛔ LA CLÉ N'EST AFFICHÉE QU'UNE FOIS : range-la immédiatement, hors dépôt. ⚠️ CE QUE TU DOIS Y FAIRE : tout essai qui ÉCRIT — commande, facture, avoir, courriel, étiquette. La vraie boutique de ton commerçant ne doit porter aucune trace de ton travail.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 201 — Prête. ⚠️ « 201 » même quand la boutique existait déjà : la clé, elle, est neuve.
  • 401 — Clé d'API requise. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « bac_a_sable_impossible » ou « cle_impossible » — avec « reason » et un « message » qui dit le geste. ⚠️ Une organisation d'essai ne peut pas avoir sa propre boutique d'essai.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — « indisponible », « reason »: « MIGRATION_ABSENTE ». Chez nous, pas chez toi : dépose-le par « POST /api/v1/defauts » et continue sans elle — réessayer ne changera rien.
Règle d'usage
Master prompt, § 2.6.

POST /api/v1/garde-fou/temoin

Déclenche un garde-fou pour de vrai, sans toucher à aucune donnée.

⭐⭐ POUR VÉRIFIER L'ENTRÉE « 2026-09-18.09 » : elle promet que le contexte d'un CRITICAL de garde-fou porte « acteurGenre », « acteurId » et « acteurNom ». Cette route écrit une VRAIE ligne CRITICAL, avec le même code et le même seuil que la production, et te rend son identifiant. ⛔ RÉSERVÉE À TA BOUTIQUE D'ESSAI : 403 avec une clé de production. Un essai ne doit jamais semer une alerte alarmante dans le journal d'un commerçant en activité. Obtiens ta clé par « POST /api/v1/boutique-d-essai ». ⚠️ AUCUNE DONNÉE N'EST TOUCHÉE, et pas « parce qu'on a fait attention » : le garde-fou juge des NOMBRES et lève AVANT toute écriture. C'est inoffensif par construction. ⛔ EN REVANCHE LA LIGNE PART BIEN DANS LE JOURNAL, donc notre guetteur horaire la voit. C'est voulu — c'est la moitié de ce qu'on prouve — mais préviens-nous si tu en joues plusieurs.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — La ligne a été écrite ; relis-la par « GET /api/v1/journaux/erreurs?level=CRITICAL ».
  • 401 — Clé d'API requise. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — « reserve_a_la_boutique_d_essai » — appelée avec une clé de production. Le message dit comment obtenir une clé d'essai. — OU, commun à toutes les routes à clé : L'organisation associée n'est pas active, OU la clé est en LECTURE SEULE et cette opération écrit (le message dit le geste : une clé avec droits d'écriture, « Réglages → API »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 10.1.

Points de vente

GET /api/v1/points-de-vente/{point}/carte

Ce qu'on trouve à ce point de vente, et à quel prix.

⭐ LES PRIX SONT DÉJÀ RÉSOLUS : « priceCents » est le prix RÉELLEMENT applicable ici, et « priceFromCatalog » dit s'il vient de la fiche produit ou d'un prix propre à ce point. Tu n'as aucun calcul à faire. ⚠️ Par défaut, seuls les produits DISPONIBLES sont rendus — c'est ce qu'un site doit afficher. « ?inclureIndisponibles=1 » rend l'état complet, pour un agent qui veut voir ce que le commerçant a décoché. ⭐⭐ LIS LES DEUX MODES : « clickAndCollect » et « phoneOrder » peuvent être VRAIS TOUS LES DEUX. Si le premier est vrai mais qu'aucun créneau n'est proposable, et que le second l'est aussi, AFFICHE LE TÉLÉPHONE — c'est le relais, et c'est ce qui empêche ta page de devenir un cul-de-sac.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — La carte de ce point.
  • 400 — Un paramètre inconnu a été envoyé.
  • 401 — Clé d'API requise. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « point_de_vente_introuvable » — aucun point ne porte ce nom dans cette boutique.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.30.

POST /api/v1/points-de-vente/{point}/carte

Reprendre la carte d'un autre point de vente.

⭐⭐ POUR NE PAS SAISIR QUATRE FOIS. Envoie « copierDepuis »: « <nom du point source> ». ⭐ ELLE AJOUTE, ELLE N'ÉCRASE PAS : les produits déjà présents à destination gardent LEUR prix et LEUR disponibilité. Écraser ferait perdre un réglage fait exprès. La réponse rend « ajoutes » et « dejaLa ».

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — « ajoutes » et « dejaLa ».
  • 400 — « copier_depuis_requis ».
  • 401 — Clé d'API requise. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Clé en LECTURE SEULE. — OU, commun à toutes les routes à clé : L'organisation associée n'est pas active, OU la clé est en LECTURE SEULE et cette opération écrit (le message dit le geste : une clé avec droits d'écriture, « Réglages → API »).
  • 404 — « point_de_vente_introuvable » — la source ou la destination.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.30.

PUT /api/v1/points-de-vente/{point}/carte

Voici la carte de ce point, en entier.

⛔⛔ REMPLACEMENT COMPLET : ce qui n'est pas dans « items » DISPARAÎT de la carte. C'est délibéré — une carte se pense comme un tout, et tu n'as ainsi aucune différence à calculer. ⚠️ « items »: [] vide la carte ; c'est légitime (un marché qui ne vend rien cette semaine) mais ça ne doit jamais être un accident. ⭐ CHAQUE LIGNE SE DÉSIGNE PAR « productId » (le nôtre) OU « externalRef » (la tienne) — au choix, pour que tu n'aies pas à tenir une table de correspondance. ⛔ « priceCents » ABSENT ou null = LE PRIX DU CATALOGUE. Ne renvoie PAS le prix que tu viens de lire : tu figerais une copie qui vieillira le jour où le commerçant changera son prix de vente, sans que rien ne le signale. ⚠️ UNE RÉFÉRENCE INCONNUE FAIT ÉCHOUER TOUT L'APPEL (422) et rien n'est écrit : nous refusons plutôt que d'ignorer, parce qu'un produit avalé en silence ne se voit qu'en regardant la vitrine.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — « count » = le nombre de produits désormais à la carte.
  • 400 — « items_requis ».
  • 401 — Clé d'API requise. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Clé en LECTURE SEULE. — OU, commun à toutes les routes à clé : L'organisation associée n'est pas active, OU la clé est en LECTURE SEULE et cette opération écrit (le message dit le geste : une clé avec droits d'écriture, « Réglages → API »).
  • 404 — « point_de_vente_introuvable ».
  • 422 — « produits_inconnus » — avec « unknownRefs ». Rien n'a été écrit.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.30.

Produits

DELETE /api/v1/products/{ref}

Supprime DÉFINITIVEMENT un produit désigné par sa référence du site, son slug ou son id.

Recherche dans l'organisation sur « externalRef », « slug » OU « id » (premier trouvé, sans ordre défini si la valeur correspond à plusieurs fiches). Ses lignes de commande et avis sont conservés, détachés du produit. Si le produit était PUBLIÉ, le site est prévenu (« catalog.product_changed ») et « propagated » vaut true.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • ref (path, obligatoire) — externalRef, slug ou id Hub. Décodé puis espaces de bord retirés.
Codes de réponse
  • 200 — Produit supprimé.
  • 400 — Référence vide après nettoyage (« Référence de produit manquante. »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Aucun produit de l'organisation ne porte cette référence (externalRef, slug ou id).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Réserves
  • Non établi depuis le code : La route applique « decodeURIComponent » à un paramètre déjà décodé : une référence contenant « % » suivi de caractères non hexadécimaux lèverait une exception non interceptée — code HTTP non établi.

GET /api/v1/products

Liste les produits VENDUS EN LIGNE de l'organisation (publiés ET en rupture par défaut) ; catalogue ENTIER sans « limit », pagination par curseur avec « limit ».

Tri par « updatedAt » décroissant. Sans « limit » : TOUT le catalogue désigné par le filtre (aucun défaut à 100). Avec « limit » : page de « limit » éléments et « nextCursor ». « cursor » est honoré même sans « limit » (tout ce qui suit le curseur). « total » compte ce que le filtre désigne, toutes pages confondues. L'appel à « unknownParameterWarnings » ne peut rien produire ici : tout paramètre inconnu est déjà refusé en 400.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • externalRef (query) — Égalité exacte sur la référence du site (0 ou 1 résultat).
  • status (query) — Absent = PUBLISHED ET OUT_OF_STOCK (depuis le contrat 2026-09-29.01 : un produit en rupture RESTE au catalogue, rendu avec « available: false »). Une valeur explicite garde son sens exact. « ALL » = tous statuts. Casse et espaces de bord ignorés. Autre valeur → 422. ⛔ Quel que soit le filtre, un produit « soldOnline: false » n'est JAMAIS rendu.
  • limit (query) — Taille de page. ABSENT = catalogue entier. Présent mais pas un entier de 1 à 200 (0, -5, abc, 10000) → 400.
  • cursor (query) — « nextCursor » de la page précédente (id du dernier produit reçu).
Codes de réponse
  • 200 — Enveloppe de liste (construite dans la route, pas par « listResponse »).
  • 400 — Paramètre inconnu (le message liste « limit », « cursor », « externalRef », « status »), ou « limit » qui n'est pas un entier de 1 à 200.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Valeur de « status » hors liste (code « invalid_filter_value »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.3.
Réserves
  • Non établi depuis le code : « cursor » désignant un id inexistant ou hors de l'organisation : réaction de Prisma (page vide ou erreur) non établie depuis le code.

POST /api/v1/products

Synchronise un lot de produits (site → Hub), idempotent par « externalRef » ; « ?dryRun=1 » simule sans rien écrire.

Ordre des contrôles : clé (écriture) → corps JSON (400) → paramètres d'URL (400) → validation (422). Corps accepté sous deux formes : tableau brut, ou objet { "products": [...] }. Lot : 1 à 1000 produits (0 à 1000 en « ?dryRun=1 »). Chaque ligne est traitée indépendamment : une ligne en échec n'arrête pas le lot. La synchronisation ne SUPPRIME jamais rien. ⚠️ « ?dryRun » n'active la simulation QUE s'il vaut exactement « 1 » : « ?dryRun=true » passe le contrôle des paramètres et ÉCRIT réellement.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — « 1 » (exactement) : essai à blanc, rien n'est écrit, la réponse est un rapport « products.dry-run ». Toute AUTRE valeur est acceptée et ÉCRIT pour de vrai.
  • deploy (query) — « 1 » (exactement) : reconstruction du site après un lot sans échec. Sans effet en mode à blanc. Toute autre valeur : aucun effet, pas de clé « deploy ».
Codes de réponse
  • 200 — Synchronisation sans ligne en échec — ou, avec « ?dryRun=1 », rapport de simulation (seconde forme).
  • 207 — Au moins une ligne a échoué (« failed » > 0, y compris quand TOUTES ont échoué). Même forme qu'en 200 ; les lignes réussies SONT écrites. Avec « ?deploy=1 » : « deploy » vaut « ignore_lot_partiellement_en_echec ».
  • 400 — Corps qui n'est pas du JSON valide ; ou paramètre d'URL autre que « dryRun » / « deploy » (y compris « limit » ou « cursor » : « Cette route ne pagine pas »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Corps invalide (« Les données d'articles sont invalides. »). « details » vient de « flatten().fieldErrors » : clés de PREMIER niveau seulement (en pratique « products »), messages sans indice de ligne.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.3.
Réserves
  • Volontairement non détaillé : Rapport à blanc : lignes[].changements[].avant / apres — valeurs brutes de tout type (un Decimal est rendu en chaîne).

POST /api/v1/stock-alerts

Inscrit un acheteur (avec son consentement) pour être prévenu du retour d'un produit ÉPUISÉ ; le Hub lui écrit une fois, quand le produit redevient disponible.

Idempotent sur produit + adresse : la réponse est IDENTIQUE que l'adresse soit nouvelle ou déjà inscrite (une inscription déjà notifiée est réarmée). Le produit est cherché dans l'organisation de la clé seulement, parmi les produits vendus en ligne et servis au site (PUBLISHED ou OUT_OF_STOCK). L'e-mail part au nom de la boutique, par sa configuration d'envoi (type « RETOUR_EN_STOCK » dans GET /api/v1/emails). Limite : 300 inscriptions nouvelles par heure et par boutique, 20 par heure et par adresse. « ?dryRun » est refusé en 400 avant la vérification de la clé.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON DISPONIBLE sur « POST /api/v1/stock-alerts » : sa seule présence (quelle que soit la valeur) est refusée en 400 par refuserDryRunNonSupporte, AVANT la vérification de la clé. Ne l'envoie pas.
Codes de réponse
  • 201 — Inscrit (ou déjà inscrit : réponse identique).
  • 400 — Corps non JSON (« Le corps de la requête doit être un objet JSON. »), ou « ?dryRun » présent.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Aucun produit vendu en ligne ne correspond à cette référence dans cette boutique (inconnu, non publié, ou « soldOnline » faux).
  • 409 — Le produit est DISPONIBLE : il n'y a rien à attendre. Rien n'est enregistré ; affiche le bouton d'achat.
  • 422 — « Inscription refusée : rien n'a été enregistré. » (details = flatten().fieldErrors : adresse invalide, identifiant absent, ou « consent » différent de true).
  • 429 — Trop d'inscriptions sur la dernière heure (boutique ou adresse). En-tête « Retry-After: 3600 ». — OU, commun à toutes les routes à clé : Trop de clés refusées depuis cette adresse : attendre le délai de « Retry-After ».
Règle d'usage
Master prompt, § 4.9 ter.

Redirections

DELETE /api/v1/redirects/{identifier}

Supprime une redirection de la boutique (par externalRef, chemin source ou id).

⭐ 30/09/2026 (2026-09-30.09). Résolution, dans CETTE boutique : « externalRef » d'abord, puis le chemin source (encodé « %2Fancienne-page » ; le « / » initial est facultatif), puis l'id. Inconnue, déjà supprimée ou d'une autre boutique → 404 (idempotent). Si le site lit les redirections au build, il doit se redéployer ensuite.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — externalRef, chemin source encodé, ou id rendu par « GET /api/v1/redirects ».
Codes de réponse
  • 200 — Supprimée.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Redirection introuvable : … » — inconnue, déjà supprimée ou d'une autre boutique.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.6.

GET /api/v1/redirects

Liste les redirections SEO ACTIVES de l'organisation (enveloppe de liste, pagination par curseur).

Tri par updatedAt décroissant ; « since » porte sur updatedAt. « includeInactive=1 » ajoute les inactives ; toute autre valeur est traitée comme absente (pas de refus). « since » ou « includeInactive=1 » comptent comme filtre pour l'indicateur de vide.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • since (query) — Filtre delta : ne rend que les éléments dont la date de référence est postérieure ou égale. Toute valeur que `new Date()` ne sait pas lire est refusée en 400 « Le filtre « since » doit être une date ISO valide. ».
  • limit (query) — Taille de page. Hors de 1..200 ou non entier : 400.
  • cursor (query) — Curseur de pagination : l'« id » du dernier élément de la page précédente (valeur de « nextCursor »).
  • includeInactive (query) — « 1 » : inclut les redirections inactives.
Codes de réponse
  • 200 — Liste des redirections.
  • 400 — Paramètre inconnu, « since » illisible, « limit » hors 1..200.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.6.

POST /api/v1/redirects

Crée ou met à jour un lot de redirections depuis le site, idempotent par « externalRef » (1 à 1000).

Accepte un tableau brut OU { "redirects": [...] }. Les chemins sont normalisés avant écriture. Mise à jour : sourcePath, destination et type sont toujours réécrits (type = PERMANENT_301 si ni « type » ni « statusCode ») ; « note » absente EFFACE la note. « ?dryRun » est refusé en 400 avant la vérification de la clé.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON DISPONIBLE sur « POST /api/v1/redirects » : sa seule présence (quelle que soit la valeur) est refusée en 400 par refuserDryRunNonSupporte, AVANT la vérification de la clé. Ne l'envoie pas.
  • deploy (query) — « 1 » : déclenche la reconstruction du site (Deploy Hook) après l'écriture, si aucune ligne du lot n'a échoué. Toute autre valeur est sans effet. L'issue est rendue dans « deploy » (et « deploy_message » si « boucle »).
Codes de réponse
  • 200 — Toutes les lignes écrites.
  • 207 — Au moins une ligne a échoué (même corps ; boucle après normalisation ou erreur reformulée par messageDErreurSurSynchro).
  • 400 — Corps non JSON, ou « ?dryRun » présent.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « Les données de redirections sont invalides. » (details = flatten().fieldErrors, erreurs de ligne sous la clé « redirects »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.6.

Réglages

GET /api/v1/mise-en-service

Rend ce qui MANQUE pour que la boutique tourne, avec les questions à poser au commerçant et les gestes que personne ne peut faire à sa place.

Deux preuves cumulées : la clé d'API (Authorization: Bearer) prouve le site ; le jeton du chemin prouve l'acheteur. La commande est cherchée par { pickupRescheduleToken: jeton, organizationId: organisation de la clé }. C'est le point de départ d'un branchement : l'agent lit « aDemander », pose les questions telles quelles, écrit les réponses par PATCH /api/v1/settings (les noms exacts sont dans « champs »), relit cette route — elle ne redemande jamais ce qui vient d'être donné — puis transmet « gestesDuGerant ». ⚠️ AUCUN SECRET N'EN SORT : chaque sujet dit « renseigné » ou « manquant », jamais la valeur.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — L'état du branchement.
  • 400 — Paramètre de requête inconnu (contrôlé avant la clé).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Organisation introuvable.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 2 bis.

GET /api/v1/referencement/sonde

Le dernier passage de la sonde du Hub sur le site : balises Google/Bing dans le HTML servi, robots.txt, sitemap.xml, clé IndexNow, outils de mesure, consentement.

Deux preuves cumulées : la clé d'API (Authorization: Bearer) prouve le site ; le jeton du chemin prouve l'acheteur. La commande est cherchée par { pickupRescheduleToken: jeton, organizationId: organisation de la clé }. Rend « latest » (le dernier passage, contrôle par contrôle) et « history » (les quatre précédents, en résumé). Chaque contrôle porte « verdict » : OK · ALERTE · ECHEC · NON_APPLICABLE · INCERTAIN · A_RENSEIGNER. « level » 1 = HTTP seulement, 2 = HTTP + vrai navigateur (rien avant l'accord, un seul page_view GA4 après, rien après le refus ; la collecte est ANNULÉE, les statistiques du gérant ne sont pas polluées). ⛔ Ne rend que les passages de TA boutique.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Les passages de la sonde.
  • 400 — Paramètre de requête inconnu.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.35.

GET /api/v1/settings

Rend tous les réglages de l'organisation de la clé ; les secrets sont remplacés par leur présence.

À lire AVANT d'écrire. Tout ce que ce GET rend s'écrit par PATCH, sauf les champs en lecture seule (identité Hub, compteurs de numérotation, CGVU, état du déploiement, de la purge, de la sauvegarde, de la fermeture, du canal) et sauf les secrets, qui s'écrivent sans jamais se relire.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Les réglages.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — L'organisation de la clé est introuvable en base (« Organisation introuvable. »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Réserves
  • Volontairement non détaillé : data.packagingOptions et data.siteCapabilities : JSON rendu tel qu'enregistré, structure non décrite ici.

PATCH /api/v1/settings

Écrit un ou plusieurs réglages de l'organisation (refus global au premier défaut) et rend l'état ENREGISTRÉ.

Accepte les 112 champs modifiables, les 8 secrets en écriture seule et « maintenance » (démonstration seulement). Ne peut ni encaisser, ni facturer, ni déplacer un compteur de numérotation, ni changer le « slug ». La réponse rend ce qui est enregistré (même forme que le GET) plus « updated ».

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — N'existe pas sur « settings » : sa seule présence (quelle que soit sa valeur) rend 400 AVANT toute écriture (refuserDryRunNonSupporte). Il n'existe que sur « POST /api/v1/products ».
  • deploy (query) — REGROUPER UN LOT D'ÉCRITURES (24/09/2026). Absent : comportement inchangé — le site est prévenu et le Deploy Hook part comme avant, et la réponse ne porte AUCUNE clé « deploy ». « 0 » : écrit et prévient le site, SANS appeler le Deploy Hook (à poser sur chaque écriture du lot sauf la dernière) ; la réponse rend « deploy »: « differe ». « 1 » : appelle le Deploy Hook une fois, même si « siteCapabilities.rebuildOn » ne réclamait pas cet évènement (à poser sur la DERNIÈRE écriture) ; la réponse rend l'issue réelle. ⛔ Toute autre valeur : 422, et RIEN n'est écrit.
Codes de réponse
  • 200 — Réglages enregistrés. « data » est relu en base par la même classification que le GET (secrets toujours masqués).
  • 400 — Corps JSON illisible ; corps qui n'est pas un objet ; aucun réglage à modifier (corps « {} ») ; ou « ?dryRun » présent.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Au moins une valeur refusée ou un nom inconnu / hors portée (dont « maintenance » hors démonstration) : « Réglages refusés : aucun n'a été enregistré. ». ⚠️ Ici « details » est un TABLEAU de phrases, pas un objet par champ. ⛔⛔ ET UN MOTIF DE REFUS NE SE DÉDUIT D'AUCUN CHAMP PRIS SÉPARÉMENT — LE CONTRASTE DU BANDEAU : si « bannerCouleurFond » et « bannerCouleurTexte » donnent un rapport de contraste INFÉRIEUR À 4,5:1 (WCAG 2.1, critère 1.4.3, niveau AA — un bandeau est du texte courant : il est petit, et on le lit en passant), le PATCH ENTIER est refusé et le message NOMME le contraste obtenu. ⭐⭐ ET C'EST L'ÉTAT COMPLET QUI EST JUGÉ, PAS SEULEMENT CE QUE TU POSTES : un appel qui ne change QUE le fond est confronté au texte DÉJÀ ENREGISTRÉ. Ne conclus donc pas d'un 200 sur une couleur que l'autre passera. ⚠️ POURQUOI CE REFUS EXISTE, ET POURQUOI IL NE SE NÉGOCIE PAS : celui qui choisit les couleurs ne verra JAMAIS le problème — il sait ce qui est écrit, son œil complète. Ce sont ses ACHETEURS qui ne liront pas, et eux ne se plaindront pas : ils passeront. Un réglage accepté sans être jugé produit une page cassée que personne ne signale. ⭐ N'ESSAIE PAS DE REFAIRE CE CALCUL CHEZ TOI pour « pré-valider » : la luminance relative de WCAG n'est pas la moyenne des trois composantes, et une moyenne naïve déclare lisible du bleu sur du noir. Poste, et lis le refus — il dit quoi faire.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 500 — Erreur non interceptée par la route (exception de la base remontée telle quelle) — corps non établi.
Réserves
  • Non établi depuis le code : 11/09/2026 : « mediaMode » (LOCAL · CLIENT) et « mailProvider » (DEFAULT · RESEND · SENDGRID · MAILGUN · SMTP) acceptent exactement les valeurs de la base ; une autre valeur, ou null / texte vide sur une colonne obligatoire, est refusée en 422 qui nomme le champ (ces cas faisaient une erreur 500).
  • Non établi depuis le code : Si l'écriture des réglages échoue après une « maintenance » valide (démonstration), la fermeture est déjà écrite et notifiée.
  • Volontairement non détaillé : data : même schéma que GET /api/v1/settings.

POST /api/v1/referencement/sonde

Lance un passage de la sonde sur TON site : HTTP tout de suite, vrai navigateur dans les cinq minutes.

Deux preuves cumulées : la clé d'API (Authorization: Bearer) prouve le site ; le jeton du chemin prouve l'acheteur. La commande est cherchée par { pickupRescheduleToken: jeton, organizationId: organisation de la clé }. Sans corps. Rend le passage HTTP (« latest », level 1) ; le passage navigateur est demandé et se relit par GET (« latest.level » vaut alors 2). Un POST par minute au plus. La sonde lit l'adresse « website » de la boutique (PATCH /api/v1/settings). « ?dryRun » est refusé (400).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Passage HTTP fait, passage navigateur demandé.
  • 400 — « ?dryRun » n'est pas disponible sur cette route.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Aucune adresse de site enregistrée (« website »), ou adresse d'un site de l'éditeur : la sonde ne sait pas où regarder.
  • 429 — trop_tot : un passage a été demandé il y a moins d'une minute (en-tête Retry-After). — OU, commun à toutes les routes à clé : Trop de clés refusées depuis cette adresse : attendre le délai de « Retry-After ».
Règle d'usage
Master prompt, § 4.35.

Réglages du site

POST /api/v1/jeton-reglages/verifier

Vérifie ET consomme le jeton reçu dans « ?jeton= » sur la page de réglages du site. Le Hub a déjà authentifié la personne.

⭐ LE SITE N'A NI COMPTE, NI MOT DE PASSE, NI ÉCRAN DE CONNEXION À CONSTRUIRE. Le commerçant clique dans son Hub, le Hub délivre un jeton court et l'envoie sur la page de réglages du site avec « ?jeton=… ». Le site appelle cette route AVEC SA CLÉ D'API et ouvre la page si « valide » est vrai. ⚠️ USAGE UNIQUE ET CINQ MINUTES : le jeton est consommé par cet appel. Ce n'est PAS une session — il autorise l'ouverture de la page, pas une connexion durable. ⚠️ UN JETON REFUSÉ N'EST PAS UNE PANNE : réponse 200 avec « valide: false », un « motif » (« inconnu », « expire », « deja_utilise », « autre_organisation ») et un « messagePourLeCommercant » déjà rédigé — l'afficher tel quel. ⚠️ Un site ne peut vérifier que SES propres jetons ; un jeton d'une autre organisation est refusé sans être consommé. ⚠️ CE JETON NE TRANSPORTE AUCUN SECRET : la clé secrète de paiement reste sur le site, elle ne vient jamais au Hub.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — « valide: true » avec « organizationId », « email », « expireAt », « note » ; ou « valide: false » avec « motif » et « messagePourLeCommercant ».
  • 400 — « json_invalide », ou « jeton_requis » si le jeton manque.
  • 401 — Clé d'API absente ou invalide. — OU, commun à toutes les routes à clé : Clé absente (« Clé d'API requise … ») ou clé invalide / révoquée (« Clé d'API invalide ou révoquée. »). Même message pour une clé inconnue et une clé révoquée, volontairement.
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, contrat 2026-09-16.29.

Remises

DELETE /api/v1/discounts/{identifier}

Supprime un code promo JAMAIS utilisé ; un code déjà consommé est refusé en 409.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — id Hub, externalRef ou code.
Codes de réponse
  • 200 — Supprimé.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Code promo introuvable. »
  • 409 — Le code a déjà servi (usedCount > 0) : il ne se supprime pas, il se désactive (« isActive »: false via POST /api/v1/discounts).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).

DELETE /api/v1/discounts/validate

Libère la réservation d'un code posée par « cartId » (panier abandonné, code changé).

Intention de clé déclarée explicitement : « read » — libérer une réservation qu'on a soi-même posée ne modifie aucune donnée du commerçant, cela rend une place. Exiger « write » obligerait les sites à employer leur clé d'écriture dans leur tunnel d'achat. ⚠️ LIBÉRER DEUX FOIS EST SANS CONSÉQUENCE, et c'est délibéré : sinon le site devrait se souvenir de ce qu'il a déjà libéré, et il s'en souviendrait mal. Une réservation absente répond 200 comme une réservation retirée. ⭐ L'échéance de quinze minutes garantit que rien ne reste bloqué POUR TOUJOURS ; elle ne rend pas le code disponible TOUT DE SUITE — il faut les deux.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — La place est rendue (même si aucune réservation n'existait).
  • 400 — Corps non JSON, ou « code »/« cartId » manquant : « error » = message de la PREMIÈRE erreur Zod.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.12.

GET /api/v1/discounts

Liste les remises (codes promo) de l'organisation, codes nominatifs EXCLUS (enveloppe de liste, pagination par curseur).

Tri par updatedAt décroissant ; « since » porte sur updatedAt. ⚠️ « personal » : le code le lit (none/all/only) mais ne le déclare pas à parseListParams — sa seule présence est donc refusée en 400 « Filtre inconnu » avant d'être lue. Effet réel : les codes nominatifs (customerEmails non vide) sont TOUJOURS exclus de cette liste. ⚠️ Seul « since » compte comme filtre pour l'indicateur de vide (« isActive=false » sans résultat peut rendre « empty_unexpected »).

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • since (query) — Filtre delta : ne rend que les éléments dont la date de référence est postérieure ou égale. Toute valeur que `new Date()` ne sait pas lire est refusée en 400 « Le filtre « since » doit être une date ISO valide. ».
  • limit (query) — Taille de page. Hors de 1..200 ou non entier : 400.
  • cursor (query) — Curseur de pagination : l'« id » du dernier élément de la page précédente (valeur de « nextCursor »).
  • isActive (query) — Insensible à la casse. Absent = « all » (actives et inactives). Autre valeur : 422 « invalid_filter_value ».
Codes de réponse
  • 200 — Liste des remises.
  • 400 — Paramètre inconnu (y compris « personal »), « since » illisible, « limit » hors 1..200.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Valeur de « isActive » non acceptée.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.5.

GET /api/v1/discounts/{identifier}

Consulte un code promo par son id, sa référence du site ou le code lui-même.

Comparaison exacte (sensible à la casse) avec id, externalRef ou code. ⚠️ Ici « value » est une CHAÎNE (Decimal.toString()), alors que la liste la rend en nombre. Les champs de ciblage et de règles d'usage de la liste ne sont PAS rendus ici.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • identifier (path, obligatoire) — id Hub, externalRef ou code.
Codes de réponse
  • 200 — Le code promo.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — « Code promo introuvable. »
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).

POST /api/v1/discounts

Crée ou met à jour un lot de remises depuis le site, idempotent par « externalRef » (1 à 1000).

Accepte un tableau brut OU { "discounts": [...] }. Mise à jour : code, type et value sont toujours réécrits ; les autres champs ne changent que s'ils sont présents (null = effacer). « ?dryRun » est refusé en 400 avant la vérification de la clé.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — NON DISPONIBLE sur « POST /api/v1/discounts » : sa seule présence (quelle que soit la valeur) est refusée en 400 par refuserDryRunNonSupporte, AVANT la vérification de la clé. Ne l'envoie pas.
  • deploy (query) — « 1 » : déclenche la reconstruction du site (Deploy Hook) après l'écriture, si aucune ligne du lot n'a échoué. Toute autre valeur est sans effet. L'issue est rendue dans « deploy » (et « deploy_message » si « boucle »).
Codes de réponse
  • 200 — Toutes les lignes écrites.
  • 207 — Au moins une ligne a échoué (même corps ; messages reformulés par messageDErreurSurSynchro, ex. collision de code « Cette valeur existe déjà et doit être unique (…). »).
  • 400 — Corps non JSON, ou « ?dryRun » présent.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « Les données de remises sont invalides. » (details = flatten().fieldErrors, erreurs de ligne sous la clé « discounts »).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.5.

POST /api/v1/discounts/validate

Valide un code saisi par l'acheteur, SANS le consommer (codes partagés et nominatifs).

Intention de clé déclarée explicitement : « read » (une clé en lecture seule suffit). Un code refusé répond 200 « valid: false » avec « reason » rédigée pour l'acheteur. Le code est mis en majuscules. Code nominatif sans « email » → refus « Ce code est personnel : renseignez l'adresse e-mail à laquelle il a été envoyé. ». Si « cartTotalCents » est fourni et inférieur au minimum : 200 « valid: false » avec « minOrderCents ». Champs inconnus du corps ignorés.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Verdict. Trois formes : valide ; refusé (« reason ») ; refusé pour minimum non atteint (« reason » + « minOrderCents »). La forme « valide » porte « reservedUntil ».
  • 400 — Corps non JSON (« Le corps de la requête n'est pas du JSON. ») OU corps invalide : « error » = message de la PREMIÈRE erreur Zod, sans « details » (ex. « Le code est obligatoire. »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 402 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.12.

Rétractations

GET /api/v1/withdrawals

Les demandes de rétractation de l'organisation et leur état (200 plus récentes au plus).

Les champs internes du traitement (motif, notes de décision) ne sortent pas. Triées de la plus récente à la plus ancienne ; aucune pagination.

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • reference (query) — Référence exacte (ex. « R-2026-000042 »).
  • customerEmail (query) — E-mail de l'acheteur, insensible à la casse.
  • status (query) — Autre valeur : 422.
  • limit (query) — Accepté par le contrôle des paramètres (refuserFiltresInconnus l'admet d'office) mais NON LU par la route : aucun effet.
  • cursor (query) — Accepté par le contrôle des paramètres (refuserFiltresInconnus l'admet d'office) mais NON LU par la route : aucun effet. La liste n'est pas paginée (« nextCursor » vaut toujours null quand il est présent).
Codes de réponse
  • 200 — Les demandes.
  • 400 — Paramètre d'URL inconnu : le message nomme le(s) paramètre(s) refusé(s) et liste ceux que la route accepte (« Filtre inconnu : … »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — « status » hors de RECEIVED · ACCEPTED · REFUSED · REFUNDED (details.status).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 6.3 decies.

POST /api/v1/withdrawals

Dépose la demande de rétractation d'un acheteur : le Hub l'horodate, l'enregistre, envoie l'accusé et prévient le gérant.

Le Hub pose lui-même la date de réception (aucun champ de date accepté). ⚠️ IRRÉVERSIBLE : un VRAI e-mail d'accusé part à « customerEmail », un avis part au gérant et le site reçoit « withdrawal.received ». Ne pas appeler pour essayer. Le site n'envoie pas de second accusé. Un numéro de commande inconnu n'est pas refusé. Les champs non décrits sont ignorés (non refusés).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — N'existe pas sur « withdrawals » : sa seule présence (quelle que soit sa valeur) rend 400 AVANT toute écriture (refuserDryRunNonSupporte). Il n'existe que sur « POST /api/v1/products ».
Codes de réponse
  • 201 — Demande enregistrée.
  • 400 — « ?dryRun » présent ; corps non JSON (« Le corps de la requête n'est pas du JSON. ») ; ou corps invalide — ⚠️ ici 400 (pas 422), « error » = message de la PREMIÈRE erreur de validation, SANS « details » (ex. « Le nom est obligatoire. », « Adresse e-mail invalide. », ou message Zod par défaut en anglais comme « Required » pour un champ absent).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Refus de fond du Hub, sans details : « Boutique introuvable. », « Indiquez une adresse e-mail valide. », ou « Votre demande n'a pas pu être enregistrée. Réessayez, ou écrivez directement au vendeur. ».
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 4.13.

Retrait sur place

GET /api/v1/pickup-reschedule/{token}

Lit, pour la page « changer de créneau » du site, la règle du commerçant et les créneaux encore proposables à l'acheteur.

Deux preuves cumulées : la clé d'API (Authorization: Bearer) prouve le site ; le jeton du chemin prouve l'acheteur. La commande est cherchée par { pickupRescheduleToken: jeton, organizationId: organisation de la clé }. Le contrôle des paramètres de requête inconnus a lieu AVANT celui de la clé. « slots » est déjà jugé (autorisation, délai de prévenance, décalage maximal, places) sur au plus 60 occurrences à venir ; vide quand plus rien n'est permis ou quand allowed vaut false. ⭐ POUR ÉPROUVER CETTE ROUTE SANS CRÉER DE COMMANDE : avec la clé de la boutique de démonstration, le jeton « demo-retrait-creneau » désigne une commande de retrait posée exprès, dont le créneau est loin dans le futur (un créneau passé rendrait « allowed: false », et tu croirais la route cassée alors qu'elle applique le délai de prévenance). ⚠️ Sur TA boutique, le jeton de chaque commande est rendu par « GET /api/v1/orders » sous « pickupRescheduleToken ».

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
  • token (path, obligatoire) — Jeton de changement de créneau de la commande (Order.pickupRescheduleToken, rendu par GET /api/v1/orders et GET /api/v1/orders/{orderNumber} sous « pickupRescheduleToken »). Cherché DANS l'organisation de la clé : un jeton d'une autre boutique (ou vide) répond 404.
  • limit (query) — Accepté d'office par refuserFiltresInconnus, non lu.
  • cursor (query) — Accepté d'office, non lu.
Codes de réponse
  • 200 — État du changement de créneau. ⭐ Lire « changeable » (pas « allowed ») pour décider d'afficher le choix, et « reason.text » pour dire POURQUOI quand il vaut false.
  • 400 — Paramètre de requête autre que limit/cursor (contrôlé avant la clé).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Jeton vide, inconnu, ou appartenant à une autre boutique que celle de la clé.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 6.3 sexies.
Réserves
  • Non établi depuis le code : Format des heures dans slots[].label : chaînes « début » / « fin » des horaires du point de retrait (supposées HH:MM, non vérifié).

POST /api/v1/pickup-reschedule/{token}

Enregistre le créneau choisi par l'acheteur (recopié d'un élément de « slots »).

Deux preuves cumulées : la clé d'API (Authorization: Bearer) prouve le site ; le jeton du chemin prouve l'acheteur. La commande est cherchée par { pickupRescheduleToken: jeton, organizationId: organisation de la clé }. Le changement est jugé par le même juge que la page du Hub (jugerChangementDeCreneau) ; en cas de succès, l'acheteur et le commerçant reçoivent un e-mail et le site un webhook « order.updated ». ⚠️ Seul « start » est jugé : « end » est enregistré tel qu'envoyé.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • token (path, obligatoire) — Jeton de changement de créneau de la commande (Order.pickupRescheduleToken, rendu par GET /api/v1/orders et GET /api/v1/orders/{orderNumber} sous « pickupRescheduleToken »). Cherché DANS l'organisation de la clé : un jeton d'une autre boutique (ou vide) répond 404.
Codes de réponse
  • 200 — Créneau changé.
  • 400 — Corps JSON illisible.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 404 — Jeton vide, inconnu, ou d'une autre boutique.
  • 422 — Trois cas. (a) start/end illisibles : details.start = ["Exemple : 2026-09-19T06:00:00.000Z"], même si c'est « end » qui est fautif ; « error » dit lui-même qu'il n'est pas pour l'acheteur, car c'est TON appel qui est en défaut, pas sa saisie. (b) jeton de DÉMONSTRATION : il ne désigne aucune commande, l'écriture ne s'éprouve que sur une vraie commande de ta boutique. (c) changement refusé par le juge (changement non autorisé, hors délai ou hors décalage, créneau non proposable ou complet, commande terminée, pas un retrait) — « error » porte le motif en français, sans details ni code, ET « messagePourAcheteur » porte LE MÊME TEXTE. ⚠ Ce n'est pas une négligence : cette page est CELLE DE L'ACHETEUSE (elle y arrive par un lien reçu par e-mail), les motifs du juge vouvoient et ne nomment aucun champ — ils sont pour elle par construction. Le champ le DIT, pour qu'aucun site n'ait à le deviner. « message » s'adresse à toi, l'intégrateur ; « messagePourAcheteur » est prêt à afficher tel quel à un consommateur. N'affiche JAMAIS « message » ni « error » à un acheteur.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 6.3 sexies.

Spécification

GET /api/v1/documentation

Rend la documentation publique à trois niveaux (découverte, mise en route, référence), en JSON.

Route PUBLIQUE : aucune clé n'est exigée — la documentation doit être lisible AVANT l'enrôlement, et par un annuaire d'applications qui n'aura jamais de clé. Elle est ENGENDRÉE depuis cette spécification et depuis la liste des capacités du Hub : elle ne peut pas décrire une route que la spécification ne décrit pas. Elle n'accepte AUCUN paramètre, pas même « limit » ni « cursor » : elle rend toujours les trois niveaux entiers. Aucun cache (« Cache-Control: no-store »).

Authentification
Aucune clé. L'opération est lisible avant même le branchement.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Les trois niveaux, plus la version du Hub, la version du contrat et la base de l'API.
  • 400 — Un paramètre de requête a été passé : cette route n'en accepte aucun. « limit » et « cursor » reçoivent un message propre (« cette route ne pagine pas »).
Réserves
  • Volontairement non détaillé : L'exemple de réponse est un EXTRAIT abrégé : le vrai document porte les trois niveaux en entier.

GET /api/v1/specification

Rend ce document : la spécification OpenAPI 3.1 de l'API publique du Hub.

Route PUBLIQUE : aucune clé n'est exigée — une spécification ne contient aucun secret et doit être lisible AVANT l'enrôlement. Elle n'accepte AUCUN paramètre, pas même « limit » ni « cursor » : elle rend toujours le document entier. Aucun cache (« Cache-Control: no-store »).

Authentification
Aucune clé. L'opération est lisible avant même le branchement.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Le document OpenAPI 3.1 complet.
  • 400 — Un paramètre de requête a été passé : cette route n'en accepte aucun. « limit » et « cursor » reçoivent un message propre (« cette route ne pagine pas »).
Réserves
  • Volontairement non détaillé : L'exemple de réponse est un EXTRAIT abrégé : le vrai document porte tous les chemins et tous les composants.

Webhook et contrat

DELETE /api/v1/webhook

Désactive la notification : efface l'URL de webhook, conserve le secret.

Aucun corps attendu. Le Deploy Hook, les capacités et le secret ne sont pas touchés.

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Notification désactivée.
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 2.

GET /api/v1/webhook

Rend le contrat complet de l'organisation : webhook, Deploy Hook, TVA, livraison, paiements, bandeau, retrait, fermeture, branchements, référencement, version du contrat.

Un objet (pas une liste). Aucun paramètre de requête n'est accepté : tout paramètre, y compris limit et cursor, est refusé en 400 (refuserFiltresInconnus(request, [], { pagine: false })). Aucun secret n'est rendu (seul secretConfigured). Tous les montants sont TTC, en centimes. Le filet unknownParameterWarnings existe dans le code mais n'est jamais atteint (le refus 400 le précède).

Authentification
Clé d'API de la boutique, en lecture. En-tête « Authorization: Bearer <clé> », depuis ton serveur et jamais depuis le navigateur.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 200 — Contrat courant de l'organisation propriétaire de la clé.
  • 400 — Un paramètre de requête est présent. Si c'est limit ou cursor : « Cette route ne pagine pas… » ; sinon : « Filtre inconnu : « … ». Cette route accepte : « ». … ».
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 503 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 2.
Réserves
  • Non établi depuis le code : deployHookRetry.callsToday : le jour comparé est la date locale du SERVEUR (toLocaleDateString("fr-CA")), dont le fuseau n'est pas établi depuis le code.
  • Non établi depuis le code : Codes 500 éventuels en cas d'erreur de base : non traités explicitement par la route.
  • Non établi depuis le code : shipping.shippingMode, carriers[].deliveryMode / modeDeTarif, sellsTo, seo.shop.mediator.status, maintenance.since_origin, connections[].kind / provider : chaînes libres en base ; seules les valeurs documentées dans les commentaires sont listées, aucune énumération n'est imposée par le code.
  • Volontairement non détaillé : capabilities : objet libre renvoyé tel que déclaré par le site (siteCapabilities).
  • Volontairement non détaillé : contract.pending[].instructions : textes du journal CONTRACT_CHANGES, rendus tels quels.
  • Volontairement non détaillé : contract.entryFingerprints : un couple version → empreinte par entrée du contrat (206 au 11/09/2026) ; l'exemple n'en montre que 2.
  • Volontairement non détaillé : contract.gatedCapabilities.closed[].whatYouMiss / stillWorking / howToOpen : textes fixes de CAPACITES_VERSIONNEES.
  • Volontairement non détaillé : bankTransfer.instructionsHtml : fragment HTML construit par construireBlocVirement (non détaillé).
  • Volontairement non détaillé : hubMention.note, maintenance.exempt.note / buyerFacing / adminPathUnknown / webhook, withdrawal.declareYourOwn.note : textes fixes en français (abrégés « … » dans l'exemple).
  • Volontairement non détaillé : pickup.points[].hours : phrase construite par phraseHoraires (format non détaillé).
  • Volontairement non détaillé : geo.countriesWithoutPostalCode : liste complète de PAYS_SANS_CODE_POSTAL (64 codes au 11/09/2026) ; l'exemple n'en montre que 3.
  • Volontairement non détaillé : seo.noIndex : 7 natures (panier, tunnel de commande, paiement, compte de l'acheteur, connexion et mot de passe, recherche interne, confirmation de commande) ; l'exemple n'en montre qu'une.

PATCH /api/v1/webhook

Refusé (405) : la configuration se déclare avec POST /api/v1/webhook.

Route publique au sens du contrôle : aucune clé n'est vérifiée, la réponse est toujours 405 avec l'en-tête « Allow: GET, POST, DELETE ».

Authentification
Aucune clé. L'opération est lisible avant même le branchement.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 405 — Toujours : méthode refusée. Le corps dit quel verbe employer.

POST /api/v1/webhook

Déclare ou remplace la configuration du site (URL de webhook, secret, Deploy Hook, dépôt de médias, gabarits d'adresse, capacités). Idempotent, rejouable.

Chaque champ n'est écrit que s'il est fourni. Le secret est toujours réécrit (fourni, sinon l'existant, sinon généré). mediaMode est recalculé et écrit à chaque appel. Les capacités sont fusionnées ; url / mediaUploadUrl / deployHookUrl fournis mettent respectivement webhooks / mediaUpload / deployHook à true. ⚠️ Un deployHookUrl fourni est appelé pour de vrai (reconstruction déclenchée). Réponse 200 (pas 201).

Authentification
Clé d'API de la boutique AVEC droits d'écriture. Une clé en lecture seule reçoit 403 sur cette opération.
Corps de la requête
Corps JSON attendu. Sa forme exacte est décrite dans la spécification machine.
Paramètres
  • dryRun (query) — Non pris en charge sur cette route : sa simple présence (quelle que soit la valeur) → 400, rien n'est écrit. Contrôlé AVANT l'authentification.
Codes de réponse
  • 200 — Configuration enregistrée.
  • 400 — Paramètre « dryRun » présent, ou corps qui n'est pas un JSON valide (« Le corps de la requête doit être un JSON valide. »).
  • 401 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 403 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
  • 422 — Quatre cas : (1) corps invalide selon le schéma — « Les données du webhook sont invalides. », « details » nomme les champs (« _demande » si aucun champ n'est fourni) ; (2) capabilities.contractVersion inconnue — « details.contractVersion » ; (3) capabilities.rebuildOn mal formé — sans « details » ; (4) Deploy Hook appelé qui répond 401/403/404/410 — forme propre { error, code: "deploy_hook_unreachable", status }. Dans tous les cas, rien n'est enregistré.
  • 429 — Réponse commune à toutes les routes à clé (voir le tableau des codes, plus bas).
Règle d'usage
Master prompt, § 2.
Réserves
  • Non établi depuis le code : Comportement si l'organisation n'est pas trouvée au moment de l'écriture (prisma.organization.update lèverait) : aucun code de réponse n'est prévu par la route.
  • Non établi depuis le code : capabilities.lastRestoreTestAt : aucun type n'est imposé par le code.
  • Volontairement non détaillé : capabilities (corps et réponse) : objet libre ; seules les clés lues par cette route ou par GET sont décrites. Les autres clés que lit le contrat (entrées déclarées, etc.) ne sont pas détaillées ici.
  • Volontairement non détaillé : unknownFieldsMessage et deployHookCheck.message : textes en français non reproduits en entier.

PUT /api/v1/webhook

Refusé (405) : la configuration se déclare avec POST /api/v1/webhook.

Route publique au sens du contrôle : aucune clé n'est vérifiée, la réponse est toujours 405 avec l'en-tête « Allow: GET, POST, DELETE ».

Authentification
Aucune clé. L'opération est lisible avant même le branchement.
Corps de la requête
Aucun corps.
Paramètres
Aucun. Tout paramètre de requête envoyé est refusé en 400 — jamais ignoré en silence.
Codes de réponse
  • 405 — Toujours : méthode refusée. Le corps dit quel verbe employer.

La même référence, pour une machine

Le document OpenAPI 3.1 complet — schémas, exemples de réponse, renvois au master prompt — est servi sans clé par GET https://www.structureweb.fr/api/v1/specification, et les trois niveaux de cette documentation par GET https://www.structureweb.fr/api/v1/documentation. Cette page est fabriquée à partir du premier : elle ne peut pas décrire une route qu'il ne décrit pas.