ToldUntold Logo
Catalog

API Client ToldUntold v1 — Guide d'intégration

Sur cette page

API REST machine-to-machine pour gérer le catalogue ToldUntold d'une marque — produits, axes de variantes, variantes et stock — depuis un PIM, un ERP ou un OMS.

Contrat formel : openapi.yaml (OpenAPI 3.1). Ce guide en est le compagnon narratif ; le fichier OpenAPI fait foi pour les formes exactes.


1. Vue d'ensemble & public visé

L'API Client (/v1/client/catalog/*) est la surface programmatique que les systèmes de back-office d'une marque utilisent pour synchroniser le catalogue ToldUntold. Elle couvre :

  • Produits — création, lecture (détail superset), mise à jour partielle, archivage.
  • Axes — la bibliothèque d'axes de variantes de la marque (ex. size, color) et leurs valeurs.
  • Sélection d'axes produit — quels axes/valeurs un produit expose.
  • Variantes — les SKU par produit, créés à l'unité ou générés en masse.
  • Stock — emplacements, ajustements en quantité absolue, journal des mouvements, vue plate de synchronisation complète, et feeds snapshot en masse.

Elle est pensée pour être familière à quiconque a intégré Shopify Admin ou Stripe : auth Bearer, clés à scopes, erreurs JSON plates à codes machine stables, pagination page/per_page, et écritures idempotentes.

Public visé : ingénieurs back-end intégrant les systèmes produit/stock d'une marque avec ToldUntold. Tout est serveur-à-serveur ; il n'y a pas de flux navigateur/OAuth ni de connexion utilisateur.

Autres domaines. Ce guide couvre le domaine catalog (/v1/client/catalog/*). Le domaine analytics (/v1/client/analytics/*) — analytics d'apprentissage journalier/contenu/utilisateur pour BI/ERP/SIRH — est documenté dans le guide d'intégration analytics. Les deux surfaces partagent les mêmes clés tuk_, l'authentification, l'enveloppe d'erreur et le mécanisme de rate-limit ; une seule clé peut porter des scopes catalog et analytics.

URL de base :

https://api.<environnement>.tolduntold.com

Remplacez <environnement> par la valeur fournie par ToldUntold (staging, production, …). Tous les chemins ci-dessous sont relatifs à cette base.


2. Authentification

2.1 Clés API Client (tuk_)

Chaque requête s'authentifie avec une clé API Client dans l'en-tête Authorization :

Authorization: Bearer tuk_<key_id>.<secret>

Une clé ressemble à tuk_9f2c1a4b6d8e0f2a4c6e8b0d.<secret-40-car>. La partie avant le point (tuk_<key_id>) est l'identifiant public (loggable sans risque) ; la partie après le point est le secret, affiché une seule fois à la création et stocké uniquement sous forme de hash SHA-256 chez nous. En cas de perte, faites tourner la clé (§2.3) — le secret est irrécupérable.

Chaque clé porte exactement une marque et un ensemble de scopes (§3). La marque n'est jamais transmise dans la requête — elle est résolue depuis la clé. C'est pourquoi aucun chemin ne contient d'identifiant de marque.

Les feed tokens (cfk_) ne sont PAS valides ici. Un token cfk_ sur /v1/client/* renvoie 401 unauthenticated avec un message renvoyant vers l'endpoint de feed stock historique. Voir §12 pour la relation entre les deux.

Pas de HMAC en v1. Contrairement au feed stock cfk_ historique (qui supporte un en-tête HMAC optionnel X-Catalog-Signature), l'API Client n'utilise pas de HMAC. La sécurité repose sur les identifiants Bearer sur TLS et l'idempotence. N'envoyez pas X-Catalog-Signature ; il est ignoré. (La signature de requête est réservée aux futurs webhooks — voir §11.)

2.2 Cycle de vie des clés (géré par le super-admin)

Les clés API Client sont provisionnées par un super-admin ToldUntold, sans self-service en v1. La gestion des clés vit sur la surface admin interne de ToldUntold, hors de cette API intégrateurs — mais voici les sémantiques à connaître :

  • Création — un super-admin émet une clé pour votre marque avec un nom, un ensemble de scopes et une expiration optionnelle (expires_at). La clé en clair est renvoyée une fois à la création. Stockez-la dans un gestionnaire de secrets.
  • Liste — clés masquées uniquement ; le secret n'est plus jamais réaffiché.
  • Révocation — pose revoked_at ; la clé cesse immédiatement d'authentifier (401).
  • Suppression — supprime définitivement l'enregistrement de la clé.

Pour obtenir, faire tourner ou révoquer une clé, contactez votre administrateur ToldUntold.

2.3 Rotation

La rotation crée une clé successeur (mêmes marque, nom, scopes et expiration), la lie à son prédécesseur (rotated_from_id), renvoie le nouveau secret une fois, et révoque l'ancienne clé de façon atomique. Utilisez-la pour la rotation planifiée des secrets ou après une fuite suspectée. Comme l'ancienne clé est révoquée dans la même opération, déployez le nouveau secret avant (ou juste après) la rotation pour éviter une interruption.

2.4 Test de fumée

Le contrôle d'identifiants le plus rapide est GET /v1/client/catalog/status — il requiert une clé valide mais aucun scope particulier, et renvoie la marque résolue, l'identifiant de clé, le nom, les scopes accordés et l'expiration :

curl -s https://api.staging.tolduntold.com/v1/client/catalog/status \
  -H "Authorization: Bearer tuk_9f2c1a4b6d8e0f2a4c6e8b0d.$SECRET"
{
  "brand_id": 8347,
  "key_id": "tuk_9f2c1a4b6d8e0f2a4c6e8b0d",
  "name": "ERP production",
  "scopes": ["catalog.products:read", "catalog.stock:write"],
  "expires_at": null
}

3. Scopes

Les scopes suivent une grammaire {domaine}.{ressource}:{action} et sont portés par la clé. Il n'y a pas de wildcard en v1.

Scope Autorise
catalog.products:read Toutes les lectures catalogue — produits, axes, sélection d'axes produit, variantes.
catalog.products:write Créer / mettre à jour / archiver des produits.
catalog.axes:write Créer / modifier / supprimer axes de marque et valeurs.
catalog.variants:write Créer / modifier / supprimer / générer des variantes et définir la sélection d'axes d'un produit.
catalog.stock:read Lire le stock : emplacements, niveaux, mouvements, stock par produit ; conditionne les champs stock additifs des lectures produit/variante (voir §3.1).
catalog.stock:write Créer / modifier des emplacements de stock ; ajuster les quantités de stock.
catalog.stock:ingest Soumettre des feeds de stock en masse.

Une clé peut porter n'importe quelle combinaison. Choisissez le minimum dont un système donné a besoin (un ERP qui ne fait que pousser du stock pourrait ne porter que catalog.stock:ingest + catalog.stock:read).

3.1 Le verrou stock:read sur les lectures produit

catalog.products:read renvoie produits et variantes avec le signal merchandising availability.status (in_stock / low_stock / out_of_stock / stale). Les quantités opérationnelles sont conditionnées à catalog.stock:read :

  • l'objet produit stock_summary, et
  • chaque variants[].availability.quantity

ne sont présents que si la clé porte aussi catalog.stock:read. Sans lui, stock_summary est omis et availability.quantity retiré (le status reste). Dimensionnez les scopes de vos clés en conséquence.

3.2 Scopes ANY-OF (au moins un)

La plupart des endpoints requièrent exactement un scope. Quelques-uns acceptent au moins un parmi plusieurs (ANY-OF). Le seul endpoint ANY-OF en v1 est le rapport de feed stock :

GET /v1/client/catalog/stock/feeds/{feed_id}

lisible par une clé portant soit catalog.stock:read soit catalog.stock:ingest — ainsi une clé ingest-only peut poster un feed et lire son propre rapport sans porter aussi le scope de lecture.

Un scope manquant donne 403 insufficient_scope ; le message liste le(s) scope(s) que l'endpoint accepte.


4. Erreurs

Toutes les erreurs utilisent une enveloppe plate :

{ "code": "validation_failed", "message": "The given data was invalid.", "errors": { "sku": ["The sku field is required."] } }
  • code — chaîne stable et lisible par machine. Branchez votre logique dessus.
  • message — lisible par un humain ; peut changer, ne le parsez pas.
  • errors — présent uniquement sur 422 validation_failed : une map champ → messages.

C'est additif à la forme standard {message, errors} que les intégrateurs cfk_ parsent déjà : les clés message/errors gardent leur sens, et code est nouveau.

4.1 Catalogue des codes d'erreur

HTTP code Signification
400 idempotency_key_required Un en-tête Idempotency-Key requis a été omis (création produit, création/génération de variante, ajustement de stock).
401 unauthenticated Clé absente, malformée, expirée ou révoquée — ou un feed token cfk_ présenté ici.
403 insufficient_scope Clé valide, mais elle n'a pas le scope requis.
404 not_found La ressource n'existe pas ou appartient à une autre marque. Aussi : écrire l'emplacement __default__.
405 method_not_allowed La méthode HTTP n'est pas autorisée sur ce chemin.
409 duplicate_sku Le SKU est déjà pris par un produit ou une variante de la marque.
409 duplicate_axis_code Un axe avec ce code existe déjà pour la marque.
409 duplicate_axis_value Une valeur avec ce code existe déjà sur l'axe.
409 axis_in_use Supprimer un axe attaché à un produit / utilisé par une variante, ou détacher un axe qu'une variante vive utilise.
409 value_in_use Supprimer une valeur utilisée par une variante ou sélectionnée par un produit.
409 conflict Une combinaison d'axes de variante existe déjà ; une course SKU pendant generate ; un emplacement de stock en double ; la rotation d'une clé révoquée.
409 stock_version_conflict expected_quantity ne correspond pas au niveau actuel. Porte current_quantity.
409 rejected_stale Un ajustement de stock a été rejeté sur un horodatage en conflit ; aucune modification faite.
409 idempotency_key_reuse L'Idempotency-Key a déjà été utilisée avec un corps de requête différent.
409 idempotency_conflict Une requête avec cet Idempotency-Key est actuellement en cours.
422 validation_failed La validation du corps/de la query a échoué. Porte errors.
422 no_axes_selected generate appelé sur un produit sans sélection d'axes.
422 variant_cap_exceeded L'expansion generate dépasse 500 combinaisons.
429 rate_limited Une limite de débit a été atteinte. Porte Retry-After et les en-têtes X-RateLimit-*.
500 server_error Erreur serveur inattendue (opaque ; ne fuit jamais d'internes).

no_axes_selected et variant_cap_exceeded sont des HTTP 422 mais portent leur propre code machine (pas validation_failed) et n'ont pas de map errors — ce sont des préconditions sémantiques, pas de la validation de champ.


5. Pagination, tri & filtres

5.1 Listes

Les endpoints de liste renvoient une enveloppe :

{
  "data": [ /* items */ ],
  "meta": { "current_page": 1, "per_page": 50, "total": 137, "last_page": 3 }
}

Les endpoints objet-unique renvoient un corps JSON plat (pas de data/meta).

  • page — base 1, défaut 1.
  • per_page — défaut 50, plafond dur à 200. Une valeur supérieure est silencieusement ramenée à 200 (pas rejetée).

(Le bloc lines du rapport de feed stock garde ses propres page/per_page, défaut 100, plafond 500.)

5.2 Tri

Les listes de produits acceptent sort= avec une liste de champs séparés par des virgules ; un - en tête signifie décroissant. Champs autorisés : sku, created_at, updated_at. Les champs inconnus sont ignorés ; l'ordre par défaut est -updated_at.

GET /v1/client/catalog/products?sort=-updated_at,sku

Les autres listes utilisent un ordre déterministe stable (documenté par endpoint dans le fichier OpenAPI) ; sort= est câblé sur les produits en v1.

5.3 Filtres & synchronisation incrémentale

Toute liste supporte updated_since pour le polling incrémental :

  • GET /products?updated_since=<iso8601> — produits modifiés à/après l'instant.
  • GET /stock/levels?updated_since=<iso8601> — niveaux dont source_updated_at est à/après l'instant.

Autres filtres par ressource : produits par sku, is_active, has_variants ; variantes par is_active, sku ; axes et emplacements par is_active ; niveaux de stock par sku et location ; stock par produit et mouvements par location et variant.


6. Idempotence

Les requêtes mutantes peuvent porter un en-tête Idempotency-Key (un jeton généré par le client, max 191 caractères) :

Idempotency-Key: 2026-07-08T09:00Z-create-tee-classic

Sémantiques :

  • Replay — une requête complétée rejouée avec la même clé et un corps identique renvoie la réponse d'origine telle quelle, plus Idempotency-Replayed: true.
  • Conflit de réutilisation — la même clé avec un corps différent donne 409 idempotency_key_reuse.
  • Conflit en vol — la même clé pendant que la première requête tourne encore donne 409 idempotency_conflict.
  • Rétention — les clés réglées sont conservées 24 heures, après quoi la clé peut être réutilisée. Une requête échouée (non-2xx) libère sa clé immédiatement, vous pouvez donc réessayer sans risque avec la même clé.

6.1 Obligatoire vs. recommandée

Idempotency-Key est OBLIGATOIRE sur les créations dont les doublons ne peuvent pas être attrapés par une contrainte de base (un renvoi après timeout forgerait sinon un vrai doublon). L'omettre là renvoie 400 idempotency_key_required :

Endpoint Idempotency-Key
POST /products Obligatoire
POST /products/{id}/variants Obligatoire
POST /products/{id}/variants/generate Obligatoire
POST /products/{id}/stock/adjust Obligatoire
PATCH /products/{id} Optionnelle (honorée)
POST /axes, POST /axes/{code}/values Optionnelle (honorée)
POST /stock/locations Optionnelle (honorée)
Autres PATCH / DELETE / PUT Non traitée — en-tête ignoré (naturellement rejouables)

Sur les endpoints « optionnelle (honorée) », une clé envoyée est rejouée comme une clé obligatoire ; l'envoyer est recommandé pour toute écriture que vous pourriez réessayer. Sur les autres PATCH/DELETE/PUT, il n'y a pas de couche d'idempotence — l'opération est naturellement rejouable, donc un en-tête Idempotency-Key est simplement ignoré (jamais une erreur).

6.2 Les feeds stock utilisent source_batch_id, pas ce mécanisme

POST /stock/feeds n'utilise pas le middleware d'idempotence générique. Il garde le mécanisme de batch de feed existant : l'en-tête Idempotency-Key est obligatoire et se replie dans source_batch_id. Re-poster le même batch id renvoie le feed existant inchangé (200). Un en-tête manquant est ici un 422 validation_failed (le champ source_batch_id est requis) — pas un 400. Voir §9.9.


7. Limites de débit

Deux limiteurs s'appliquent, par minute :

Bucket Limite Notes
Lectures par clé 300 / min GET/HEAD.
Écritures par clé 60 / min POST/PATCH/PUT/DELETE.
Agrégat par marque 600 / min Sur toutes les clés de la marque, toutes méthodes.

Chaque réponse porte les en-têtes du bucket le plus contraint applicable à l'appel :

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 297
X-RateLimit-Reset: 1751965200

X-RateLimit-Reset est un epoch Unix (secondes). Lorsqu'une limite est dépassée, vous obtenez 429 rate_limited avec le même trio X-RateLimit-* (remaining 0) plus Retry-After (secondes). Patientez jusque-là.


8. Sémantiques d'une API de gestion

C'est une API de gestion, pas une API de lecture storefront. Deux conséquences :

  • Les ressources inactives sont exposées. Produits, axes, valeurs, variantes et emplacements sont renvoyés quel que soit leur is_active — un PIM doit voir tout ce qu'il gère, actif ou non. Utilisez les filtres is_active pour affiner.
  • is_active est votre interrupteur de visibilité. L'archivage est un état souple, pas une suppression : PATCH /products/{id} avec is_active: false archive un produit ; is_active: true le republie. Variantes et emplacements se comportent pareil. Seules les variantes ont un vrai DELETE (soft) ; les produits sont archivés, jamais supprimés, en v1.
  • PATCH est partiel, PUT est déclaratif. PATCH ne touche que les champs envoyés. Le seul PUT (sélection d'axes produit) est un remplacement déclaratif complet — envoyez l'ensemble désiré complet ; tout ce qui est omis est retiré.

9. Parcours par ressource (bout-en-bout)

Un flux complet d'onboarding de marque avec curl. À définir une fois :

BASE=https://api.staging.tolduntold.com/v1/client/catalog
KEY="tuk_9f2c1a4b6d8e0f2a4c6e8b0d.$SECRET"
auth=(-H "Authorization: Bearer $KEY" -H "Content-Type: application/json")

9.1 Créer un produit

Idempotency-Key obligatoire. is_active défaut false ; sku obligatoire et unique dans la marque (produits et variantes).

curl -s "$BASE/products" "${auth[@]}" \
  -H "Idempotency-Key: onboard-tee-classic-1" \
  -d '{
    "sku": "TEE-CLASSIC",
    "title": "Classic Tee",
    "type": "simple",
    "price_value": 29.90,
    "price_currency": "EUR",
    "is_active": true,
    "default_language": "en-US",
    "versions": { "fr-FR": { "title": "Tee Classique" } }
  }'

201 renvoie le détail superset : champs produit core plus has_variants, price_range, availability, axes, variants, et (avec catalog.stock:read) stock_summary. Capturez son tu_id :

PID=$(curl -s "$BASE/products?sku=TEE-CLASSIC" "${auth[@]}" | jq -r '.data[0].tu_id')

9.2 Créer des axes et des valeurs

curl -s "$BASE/axes" "${auth[@]}" -d '{
  "code": "size", "label": "Size",
  "values": [ {"value":"S","label":"Small"}, {"value":"M","label":"Medium"}, {"value":"L","label":"Large"} ]
}'

curl -s "$BASE/axes/size/values" "${auth[@]}" -d '{ "value": "XL", "label": "Extra Large" }'

code et value sont URL-safe et immuables ([a-zA-Z0-9._-]+). Un doublon donne 409 duplicate_axis_code / duplicate_axis_value.

9.3 Sélectionner les axes sur le produit (PUT déclaratif)

Définit quels axes/valeurs le produit expose. Ordre du tableau = position de l'axe.

curl -s -X PUT "$BASE/products/$PID/axes" "${auth[@]}" -d '{
  "axes": [ { "code": "size", "values": ["S","M","L"] } ]
}'

200 renvoie la sélection effective. Un axes: [] vide détache tout. Codes/valeurs inconnus ou inactifs → 422 ; détacher un axe qu'une variante vive utilise encore → 409 axis_in_use.

9.4 Générer des variantes (cartésien)

Prévisualisez d'abord avec dry_run, puis créez. Idempotency-Key obligatoire.

curl -s "$BASE/products/$PID/variants/generate" "${auth[@]}" \
  -H "Idempotency-Key: gen-tee-1" \
  -d '{ "dry_run": true }'
# → { "total_combinations": 3, "would_create": 3, "skipped_existing": 0 }

curl -s "$BASE/products/$PID/variants/generate" "${auth[@]}" \
  -H "Idempotency-Key: gen-tee-1-commit" \
  -d '{ "sku_template": "{sku}-{size}", "activate": true }'

201 renvoie { total_combinations, created, skipped_existing, variants: [...] }. sku_template supporte {sku} (SKU/tu_id produit) et les placeholders {axis_code} ; les combinaisons existantes sont ignorées. Plus de 500 combinaisons → 422 variant_cap_exceeded ; aucun axe sélectionné → 422 no_axes_selected.

Créer une variante unique à la place :

curl -s "$BASE/products/$PID/variants" "${auth[@]}" \
  -H "Idempotency-Key: var-tee-xl-1" \
  -d '{ "sku": "TEE-CLASSIC-XL", "axis_values": { "size": "XL" }, "price": { "value": 32.00 } }'

axis_values mappe chaque code d'axe sélectionné à une valeur. Une collision de SKU → 409 duplicate_sku ; une combinaison existante → 409 conflict. axis_values est immuable en PATCH (recréez pour la changer) ; price.value: null en PATCH efface la surcharge pour que la variante hérite du prix produit.

9.5 Créer un emplacement de stock

curl -s "$BASE/stock/locations" "${auth[@]}" \
  -d '{ "external_code": "store-paris", "name": "Paris Flagship" }'

external_code est obligatoire, URL-safe, unique dans la marque et immuable. C'est l'adresse utilisée dans PATCH /stock/locations/{external_code}. Le sentinel réservé __default__ ne peut être créé (422) ni écrit (404).

9.6 Pousser un feed de stock (snapshot en masse)

Requiert catalog.stock:ingest. L'en-tête Idempotency-Key est le batch id.

curl -s "$BASE/stock/feeds" "${auth[@]}" \
  -H "Idempotency-Key: 2026-07-08T09:00Z-batch-001" \
  -d '{
    "source": "erp-eu",
    "mode": "snapshot",
    "lines": [
      { "sku": "TEE-CLASSIC-S", "location_code": "store-paris", "quantity": 12, "reserved": 2 },
      { "sku": "TEE-CLASSIC-M", "location_code": "store-paris", "quantity": 7 }
    ]
  }'

202 accuse réception et met en file :

{ "feed_id": "tuf_5e6f7a8b", "status": "queued", "received_lines": 2,
  "report_url": "/v1/client/catalog/stock/feeds/tuf_5e6f7a8b" }

Re-poster le même Idempotency-Key renvoie le feed existant (200). Seul le mode snapshot est supporté en v1 (delta422).

9.7 Lire le rapport de feed

Lisible avec stock:read ou stock:ingest :

curl -s "$BASE/stock/feeds/tuf_5e6f7a8b" "${auth[@]}"
{
  "feed_id": "tuf_5e6f7a8b", "source": "erp-eu", "status": "completed",
  "totals": { "applied": 2 }, "report_url": "/v1/client/catalog/stock/feeds/tuf_5e6f7a8b",
  "lines": { "data": [ { "sku": "TEE-CLASSIC-S", "location_code": "store-paris", "result": "applied", "message": null } ] }
}

9.8 Ajuster le stock (set absolu, verrou optimiste)

Requiert catalog.stock:write. Idempotency-Key obligatoire. quantity est la valeur absolue à poser ; reserved est préservé et non modifiable. Passez expected_quantity pour un garde-fou de verrou optimiste.

# variant est obligatoire pour un produit à variantes ; omis/null pour un simple.
curl -s "$BASE/products/$PID/stock/adjust" "${auth[@]}" \
  -H "Idempotency-Key: adj-2026-07-08-1" \
  -d '{ "variant": "tuv_1a2b3c4d", "location": "store-paris", "quantity": 42, "expected_quantity": 40 }'

200 renvoie { level, movement }. Si le niveau a changé depuis votre lecture → 409 stock_version_conflict portant current_quantity. Un location null cible le sentinel __default__ (auto-créé).

9.9 Relire le stock

# Vue plate cross-produit de synchro complète (paginée, filtrable, updated_since).
curl -s "$BASE/stock/levels?location=store-paris" "${auth[@]}"

# Vue par produit : variantes × emplacements + lignes produit + disponibilité agrégée.
curl -s "$BASE/products/$PID/stock" "${auth[@]}"

# Journal des mouvements, plus récent d'abord.
curl -s "$BASE/products/$PID/stock/movements" "${auth[@]}"

Chaque niveau porte quantity, reserved, et le available calculé = max(0, quantityreserved).


10. Notes de cohérence

  • 404 plutôt que 403 pour le cross-brand. Toute ressource hors de la marque de votre clé est un 404 not_found plat — vous ne pouvez jamais distinguer « n'existe pas » de « appartient à une autre marque ». Aucun accès cross-brand.
  • Pas de création implicite sur les lectures. Les lectures ne mutent jamais. stock/adjust avec un location null est le seul endroit où un emplacement __default__ est auto-créé.
  • Lectures superset. Les lectures produit renvoient le superset ToldUntold complet (champs core intacts, plus les clés additives). Un produit que vous venez d'écrire revient dans la forme exacte qu'un GET ultérieur renverrait.

11. Hors périmètre en v1 & roadmap

Non disponible en v1 — ne construisez rien dessus ; c'est documenté pour que vous puissiez planifier. Les champs marqués réservé seront additifs (sans risque à ignorer aujourd'hui) :

Domaine Statut en v1
Médias produit Non modifiable. Roadmap.
Catégories Non modifiable. Roadmap.
Marchés / audiences Non modifiable. Roadmap.
DELETE produit Non disponible — archivez avec is_active: false.
Upsert par SKU Roadmap : PUT /products/by-sku/{sku} (v1.1).
Webhooks Réservé. Utilisera la signature HMAC de requête (schéma de signature non utilisé en v1).
Pagination par curseur Réservé. La v1 utilise page/per_page.
Disponibilité par marché Champ additif réservé availability_by_market ; aujourd'hui la disponibilité est un agrégat global.
Scopes wildcard Non supportés ; énumérez les scopes explicitement.
Attachement POS aux emplacements Lecture seule (pointofsale_tu_id) ; non modifiable en v1.
Feeds stock delta Non supporté ; envoyez des feeds snapshot complets.

Comme les erreurs sont un contrat {code} stable et les lectures des supersets additifs, les ajouts futurs ne casseront pas une intégration v1 qui ignore les champs inconnus.


12. Relation avec le feed stock cfk_ historique

Avant l'API Client, la seule surface machine était l'ingestion de stock via les feed tokens cfk_ sur POST /v1/catalog/stock/feeds. Ce chemin est inchangé et byte-stable — les intégrations cfk_ existantes continuent de fonctionner exactement comme avant (même auth, même corps, HMAC optionnel, même source_batch_id idempotent).

L'API Client ajoute un feed stock superset sur POST /v1/client/catalog/stock/feeds (scope catalog.stock:ingest) avec le même corps de requête, plus tout le reste (produits, axes, variantes, ajustements, lectures). Les nouvelles intégrations doivent utiliser les clés tuk_ et l'API Client ; l'émission de nouveaux tokens cfk_ est en cours d'arrêt.

La migration est à faible risque : le corps du feed est identique, donc basculer une poussée de stock de cfk_//v1/catalog/stock/feeds vers tuk_//v1/client/catalog/stock/feeds revient essentiellement à échanger le token et l'URL (et à retirer tout en-tête HMAC). La sémantique Idempotency-Key = source_batch_id est reprise à l'identique.

Divergence documentée (chemin historique). La documentation cfk_ historique indiquait que les feed tokens vérifient des « abilities » par token ; en pratique le middleware cfk_ historique ne vérifie pas les abilities. C'est une propriété connue du chemin historique et son comportement est inchangé. L'API Client est différente : elle applique les scopes à chaque requête (§3), donc une clé tuk_ ne peut réellement pas agir hors de ses scopes accordés.


Annexe — référence rapide des endpoints

Méthode & chemin Scope(s) Clé Idem
GET /status toute clé valide
GET /products products:read
GET /products/{id} products:read
POST /products products:write requise
PATCH /products/{id} products:write option.
GET /axes products:read
POST /axes axes:write option.
PATCH /axes/{code} axes:write
DELETE /axes/{code} axes:write
POST /axes/{code}/values axes:write option.
PATCH /axes/{code}/values/{value} axes:write
DELETE /axes/{code}/values/{value} axes:write
GET /products/{id}/axes products:read
PUT /products/{id}/axes variants:write
GET /products/{id}/variants products:read
POST /products/{id}/variants variants:write requise
POST /products/{id}/variants/generate variants:write requise
PATCH /variants/{id} variants:write ignorée
DELETE /variants/{id} variants:write ignorée
GET /stock/locations stock:read
POST /stock/locations stock:write option.
PATCH /stock/locations/{external_code} stock:write
GET /stock/levels stock:read
GET /products/{id}/stock stock:read
GET /products/{id}/stock/movements stock:read
POST /products/{id}/stock/adjust stock:write requise
POST /stock/feeds stock:ingest requise¹
GET /stock/feeds/{id} stock:read ou stock:ingest

¹ L'Idempotency-Key du feed est le source_batch_id (mécanisme de feed, pas le middleware d'idempotence générique) ; un en-tête manquant est un 422, pas un 400.

Formation. Intelligence. Production de contenu. Visual merchandising. IA. Une seule plateforme, au service de la performance retail.