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éstuk_, 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 tokencfk_sur/v1/client/*renvoie401 unauthenticatedavec 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 optionnelX-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 pasX-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 sur422 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_selectedetvariant_cap_exceededsont des HTTP422mais portent leur propre code machine (pasvalidation_failed) et n'ont pas de maperrors— 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éfaut1.per_page— défaut50, 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 dontsource_updated_atest à/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 filtresis_activepour affiner. is_activeest votre interrupteur de visibilité. L'archivage est un état souple, pas une suppression :PATCH /products/{id}avecis_active: falsearchive un produit ;is_active: truele republie. Variantes et emplacements se comportent pareil. Seules les variantes ont un vraiDELETE(soft) ; les produits sont archivés, jamais supprimés, en v1.- PATCH est partiel, PUT est déclaratif.
PATCHne touche que les champs envoyés. Le seulPUT(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 (delta → 422).
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,
quantity − reserved).
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_foundplat — 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/adjustavec 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
GETulté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 middlewarecfk_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.