Un produit a un SKU, un prix de liste, et des informations supplémentaires facultatives comme marque, catégorie, statut de stock et détails supplémentaires. Les prix sont toujours calculés pour vous : voir Prix.
Les produits peuvent avoir aussi familyId et orderStep (par défaut 1). Une lecture d'un seul produit inclut des métadonnées family facultatives avec son nom, les noms des attributs de variante et les préréglages. Un prix configuré contient orderingFields, configurationRequired, definitionVersion et un prix client facultatif quantityBreaks. Les champs de tri sont des entrées de l'acheteur ; details restent des faits sur le produit.
POST/api/v1/products/{id}/quote
Cette opération en lecture seule accepte { "customer": "ACME01", "qty": 3, "configuration": { "length": 2.75, "reference": "Kitchen" } }. customer est facultatif pour la tarification par liste du personnel. Les clés en lecture seule peuvent l'utiliser. Elle retourne data.productId, qty, fields validés, definitionVersion et price avec unit, line et la spécification choisie. Les champs requis manquants renvoient Configurez pour le prix ; les combinaisons invalides sont refusées. Elle ne crée pas de panier, de commande ou de paiement. Utilisez price.line plutôt que de multiplier le prix unitaire.
Les imports acceptent des family, orderStep et orderingDefinition facultatifs. Une définition utilise le schéma d'OpenAPI, nécessite le Pro et les autorisations de tarification globale, et retourne ruleDrafts pour révision via le processus de changement de prix existant. Une structure omise laisse les paramètres existants inchangés. Voir configuration du catalogue de commerce.
L'objet produitLien vers cette section#
{
"id": "3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c",
"sku": "HX-8510",
"name": "Hex bolt M10 x 50 zinc",
"description": "Grade 8.8, zinc plated, fully threaded.",
"brandId": "a1b2c3d4-0000-4000-8000-000000000001",
"categoryId": "a1b2c3d4-0000-4000-8000-000000000002",
"manufacturerRef": "DIN933-M10-50",
"unitLabel": "Box of 100",
"minOrderQty": 1,
"availability": "in_stock",
"stockQty": 240,
"lowStockAt": null,
"leadTimeNote": null,
"dealText": null,
"active": true,
"visibility": "all",
"details": { "Finish": "Zinc", "Thread": "M10" },
"createdAt": "2026-08-14T09:30:00.000Z",
"updatedAt": "2026-09-20T16:02:11.000Z",
"price": {
"unit": 1850,
"line": 1850,
"list": 1850,
"discountBp": 0,
"source": "list",
"label": ""
}
}
| Champ | Type | Description |
|---|---|---|
id | chaîne | Identifiant du produit. |
sku | chaîne | Votre code de stock. Unique dans l'espace de travail, indépendamment de la casse. |
name | chaîne | Nom du produit. |
description | chaîne ou null | Description plus longue. |
brandId, categoryId | chaîne ou null | Voir marques et catégories. |
manufacturerRef | chaîne ou null | Numéro de pièce du fabricant. Recherchable comme le SKU. |
unitLabel | chaîne | Ce qu’est une unité, par exemple « Chaque » ou « Boîte de 100 ». |
minOrderQty | entier | Quantité minimale que les acheteurs peuvent commander. |
availability | chaîne | not_tracked, in_stock, low_stock, out_of_stock ou made_to_order. Une étiquette uniquement pour les acheteurs : chaque produit peut être commandé. L’envoi sans stockQty efface tout compteur stocké (vous définissez manuellement le statut). Avec un stockQty, il est calculé pour vous : 0 ou moins est out_of_stock, jusqu’au seuil bas est low_stock, au-dessus de ce seuil in_stock. made_to_order n’est jamais modifié par le nombre. |
stockQty | entier ou null | Unités en stock, ou null si vous ne comptez pas ce produit. Lorsqu’il est défini, availability lui est associé. Omis lorsque vous demandez en tant que client (customer). |
lowStockAt | entier ou null | Niveau de stock propre à ce produit, ou null pour utiliser le niveau de l’espace de travail. Omis lorsque vous demandez en tant que client. |
leadTimeNote | chaîne ou null | Par exemple « 3–5 jours ouvrables ». |
dealText | chaîne ou null | Une note affichée à côté du prix, telle que « min 12 btls 45 ». Son titre est l’étiquette de l’offre de l’espace de travail (Paramètres, Affichage du catalogue). |
active | booléen | Les produits inactifs sont masqués des acheteurs. |
visibility | chaîne | all, ou selected lorsqu’uniquement certains clients peuvent le voir. |
details | objet | Colonnes supplémentaires provenant des importations, comme { "Finish": "Zinc" }. |
price | objet ou null | Le prix à la quantité 1 (ou qty). Voir Prix. unit est null lorsqu’un produit n’a pas encore de prix. |
Les comptes de stock sont à votre charge pour les maintenir à jour : les commandes ne modifient jamais stockQty, envoyez-le donc depuis votre système de stock chaque fois qu’il change. Les clients ne voient que le statut availability, jamais le nombre. À zéro, un produit peut toujours être commandé ; il est simplement étiqueté comme épuisé.
GET /products/{id} inclut également les noms brand et category.
Les images ne sont pas renvoyées par l’API. Vous pouvez en définir une avec imageUrl lors de la création ou de la mise à jour.
Liste ou recherche de produitsLien vers cette section#
GET/api/v1/products
Sans q, les produits sont listés par nom. Avec q, les résultats s’affichent dans cet ordre :
- SKU exact ou référence du fabricant
- SKU ou référence du fabricant commençant par
q - recherches par mots-clés sur le nom, la marque, la catégorie et la description (3 caractères ou plus ; les requêtes plus courtes correspondent au début du nom)
Passez customer pour définir le prix de chaque ligne pour ce client et ne retournez que les produits que ce client peut voir (actifs, et soit pour tout le monde, soit sélectionnés pour lui). C’est exactement ce que le client voit dans leur portail de commerce.
| Nom | Type | Obligatoire | Description |
|---|---|---|---|
q | chaîne | non | SKU, référence fabricant, nom, marque ou catégorie. Jusqu'à 200 caractères. |
category | chaîne | non | Identifiant de catégorie. |
brand | chaîne | non | Identifiant de marque. |
availability | chaîne | non | Une des valeurs d'indisponibilité. |
active | true ou false | non | true retourne uniquement les produits actifs. La valeur par défaut est true lorsqu'customer est définie ; sinon, tous les produits sont retournés. |
customer | chaîne | non | Identifiant du client ou code de compte. |
limit | entier | non | 1 à 50. Valeur par défaut 50. |
offset | entier | non | Lignes à ignorer. Valeur par défaut 0. |
curl "https://tradecatalog.app/api/v1/products?q=hex%20bolt&customer=OAK01&limit=20" \
-H "Authorization: Bearer $TRADECATALOG_API_KEY"
const params = new URLSearchParams({
q: 'hex bolt',
customer: 'OAK01',
limit: '20',
})
const res = await fetch(`https://tradecatalog.app/api/v1/products?${params}`, {
headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: products, nextOffset } = await res.json()
{
"data": [
{
"id": "3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c",
"sku": "HX-8510",
"name": "Hex bolt M10 x 50 zinc",
"description": "Grade 8.8, zinc plated, fully threaded.",
"brandId": "a1b2c3d4-0000-4000-8000-000000000001",
"categoryId": "a1b2c3d4-0000-4000-8000-000000000002",
"manufacturerRef": "DIN933-M10-50",
"unitLabel": "Box of 100",
"minOrderQty": 1,
"availability": "in_stock",
"leadTimeNote": null,
"active": true,
"visibility": "all",
"details": { "Finish": "Zinc", "Thread": "M10" },
"createdAt": "2026-08-14T09:30:00.000Z",
"updatedAt": "2026-09-20T16:02:11.000Z",
"price": {
"unit": 1573,
"line": 1573,
"list": 1850,
"discountBp": 1500,
"source": "brand",
"label": "Brand discount −15%"
}
}
],
"nextOffset": null,
"currency": "GBP"
}
Obtenir un produitLien vers cette section#
GET/api/v1/products/{id}
| Nom | Dans | Type | Obligatoire | Description |
|---|---|---|---|---|
id | chemin | chaîne | oui | Identifiant du produit. |
customer | requête | chaîne | non | Identifiant du client ou code de compte. Retourne 404 si ce client ne peut pas voir le produit. |
qty | requête | entier | non | Quantité à facturer, de 1 à 1 000 000. Valeur par défaut 1. price.line est unit × qty. |
curl "https://tradecatalog.app/api/v1/products/3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c?customer=OAK01&qty=5" \
-H "Authorization: Bearer $TRADECATALOG_API_KEY"
const res = await fetch(
`https://tradecatalog.app/api/v1/products/${productId}?customer=OAK01&qty=5`,
{ headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` } },
)
const { data: product } = await res.json()
{
"data": {
"id": "3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c",
"sku": "HX-8510",
"name": "Hex bolt M10 x 50 zinc",
"description": "Grade 8.8, zinc plated, fully threaded.",
"brandId": "a1b2c3d4-0000-4000-8000-000000000001",
"categoryId": "a1b2c3d4-0000-4000-8000-000000000002",
"manufacturerRef": "DIN933-M10-50",
"unitLabel": "Box of 100",
"minOrderQty": 1,
"availability": "in_stock",
"leadTimeNote": null,
"active": true,
"visibility": "all",
"details": { "Finish": "Zinc", "Thread": "M10" },
"createdAt": "2026-08-14T09:30:00.000Z",
"updatedAt": "2026-09-20T16:02:11.000Z",
"brand": "Northgate",
"category": "Bolts",
"price": {
"unit": 1573,
"line": 7865,
"list": 1850,
"discountBp": 1500,
"source": "brand",
"label": "Brand discount −15%"
}
},
"currency": "GBP"
}
Pour rechercher un produit par SKU, utilisez GET /products?q=HX-8510 : une correspondance exacte de SKU est toujours prioritaire. Ou utilisez GET /prices, qui prend un SKU.
Créer ou mettre à jour des produitsLien vers cette section#
PUT/api/v1/productsclé d'écriture
Envoyez jusqu'à 100 produits par appel. Chacun est associé à un produit existant par SKU, en ignorant la casse. S'il n'y a pas de correspondance, un nouveau produit est créé. Les marques et les catégories sont associées par nom et créées si elles n'existent pas.
Comment les prix sont gérésLien vers cette section#
| Le produit… | Ce qui se passe avec le prix |
|---|---|
| est nouveau | Devient actif à listPrice immédiatement. |
| existe mais n’a pas encore de prix | Devient actif à listPrice immédiatement. |
| existe au même prix | Rien ne se passe. |
| existe à un prix différent | Rien ne change pour l'instant. Le nouveau prix est placé dans un changement de prix en brouillon pour cette liste. priceChangeId est le brouillon pour la liste de listPrice. priceChangesByList a une entrée par liste modifiée. |
Les changements de prix en brouillon sont comment TradeCatalog empêche une erreur de frappe d'atteindre les clients. Le propriétaire ou le personnel examine le brouillon dans l'application (Changements de prix), vérifie les plus grandes variations et les avertissements, choisit une date et si les clients doivent être informés par courriel, puis publie. Vous pouvez prévisualiser un brouillon avec GET /price-changes/{id}.
Une synchronisation, un brouillon par liste
Un appel qui modifie les prix démarre un brouillon par liste, appelé « Import API (nom de clé) » pour le plan Standard et avec le nom de la liste lorsqu'on en envoie plus d'une. Pour garder une synchronisation entière dans ces brouillons, envoyez priceChangeId (la liste pour listPrice) et drafts (les autres listes) depuis le premier appel avec chaque appel ultérieur. Chaque appel doit toujours être un brouillon : une fois publié, annulé ou supprimé, laissez cet identifiant de côté pour en démarrer un nouveau.
CorpsLien vers cette section#
| Champ | Type | Requis | Description |
|---|---|---|---|
products | tableau | oui | 1 à 100 produits. Les SKU doivent être uniques dans l'appel. |
priceChangeId | chaîne | non | Un brouillon pour la liste listPrice d'un appel antérieur. Les prix modifiés pour cette liste y sont ajoutés. 404 si elle n'existe pas, 400 si ce n'est plus un brouillon. |
drafts | tableau | non | Jusqu'à 30 entrées { list, priceChangeId } afin que les autres listes continuent leurs propres brouillons. |
products[].sku | chaîne | oui | Jusqu'à 64 caractères. |
products[].name | chaîne | oui | Jusqu'à 200 caractères. |
products[].listPrice | entier ou null | oui | Unités mineures, de 0 à 1 000 000 000. null est le prix sur demande : un nouveau produit n’a pas de prix, un produit existant conserve le sien. |
products[].priceList | chaîne ou null | non | Nom d'une liste de prix existante pour listPrice. Omettre ou null est Standard. Un nom inconnu est rejeté. |
products[].listPrices | tableau | non | Autres listes, comme { list, price }. price est des unités mineures, ou null pour laisser cette liste inchangée. Un nom inconnu est rejeté. |
products[].unitLabel | chaîne | oui | Jusqu'à 60 caractères, par exemple « Chaque ». |
products[].brand | chaîne | non | Nom de marque, jusqu'à 120 caractères. Effacé si omis. |
products[].category | chaîne | non | Nom de catégorie, jusqu'à 120 caractères. Effacé si omis. |
products[].manufacturerRef | chaîne | non | Jusqu'à 120 caractères. Effacé si omis. |
products[].description | chaîne | non | Jusqu'à 4 000 caractères. Effacé si omis. |
products[].availability | chaîne | non | Une des valeurs d'indisponibilité. Conservé si omis ; les nouveaux produits commencent par not_tracked. |
products[].stockQty | entier ou null | non | Unités en stock, de -1 000 000 à 1 000 000 000. Définit availability (voir l'objet produit) ; un statut que vous envoyez avec est ignoré, sauf made_to_order. Conservé si omis, sauf si vous envoyez un availability sans lui : alors le compte est effacé et ce statut est utilisé. null arrête le comptage ; le availability envoyé dans la même ligne s'applique, ou le statut actuel s'il n'y en a pas. |
products[].lowStockAt | entier ou null | non | Niveau de stock bas pour ce produit, à partir de 0. null utilise le niveau de l'espace de travail (Paramètres, 5 au départ). Conservé si omis. |
products[].leadTimeNote | chaîne | non | Jusqu'à 200 caractères. Conservé si omis. |
products[].dealText | chaîne | non | Jusqu'à 200 caractères, affichés à côté du prix. Conservé si omis. |
products[].details | objet | non | Jusqu'à 30 colonnes supplémentaires : noms jusqu'à 60 caractères, valeurs jusqu'à 500. Remplace toutes les détails lors de l'envoi ; conservé si omis. |
products[].imageUrl | chaîne | non | Une URL d'image https. Copiée dans TradeCatalog en arrière-plan lorsqu'elle change. Conservé si omis. |
products[].onlyFor | tableau | non | Jusqu'à 200 codes de compte ou adresses e-mail de contact. Rend le produit visible uniquement pour ces clients, remplaçant toute liste précédente. Conservé si omis. |
Envoyez chaque champ qui vous intéresse
brand, category, manufacturerRef et description sont écrits tels qu'envoyés, donc laisser l'un d'eux vide le vide. Envoyez le produit complet à chaque fois, comme une ligne de feuille de calcul.
Ces éléments ne peuvent pas encore être modifiés via l'API : rendre un produit inactif, minOrderQty, supprimer une image, effacer une note de délai de livraison, et rendre à nouveau visible un produit réservé aux clients. Faites-le dans l'application.
curl -X PUT https://tradecatalog.app/api/v1/products \
-H "Authorization: Bearer $TRADECATALOG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"products": [
{
"sku": "HX-8510",
"name": "Hex bolt M10 x 50 zinc",
"listPrice": 1950,
"unitLabel": "Box of 100",
"brand": "Northgate",
"category": "Bolts",
"manufacturerRef": "DIN933-M10-50",
"availability": "in_stock",
"details": { "Finish": "Zinc", "Thread": "M10" }
},
{
"sku": "OAK-HNG-75",
"name": "Oak-finish butt hinge 75mm",
"listPrice": 695,
"unitLabel": "Pair",
"category": "Hinges",
"onlyFor": ["OAK01"]
}
]
}'
const res = await fetch('https://tradecatalog.app/api/v1/products', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ products }),
})
const { data: result } = await res.json()
if (result.priceChangeId)
console.log('Review the draft price change in TradeCatalog')
{
"data": {
"created": 1,
"updated": 1,
"priceChanges": 1,
"priceChangeId": "c4d5e6f7-1234-4abc-9def-0123456789ab",
"priceChangesByList": [
{
"list": "Standard",
"priceChangeId": "c4d5e6f7-1234-4abc-9def-0123456789ab"
}
],
"unknownCustomers": []
}
}
| Champ | Description |
|---|---|
created | Nouveaux produits. |
updated | Produits existants correspondant au SKU. |
priceChanges | Nombre de prix intégrés dans un brouillon, sur chaque liste de l'appel. |
priceChangeId | Le brouillon pour la liste listPrice, ou null si cette liste n'avait pas de modifications. |
priceChangesByList | Un { list, priceChangeId } pour chaque liste ayant eu un changement de prix dans cet appel. |
unknownCustomers | Valeurs dans onlyFor qui n'ont pas trouvé de client (jusqu'à 50). Ces clients n'ont pas eu accès. |
Les modifications apparaissent dans le journal d'activité sous le nom de la clé, et les recherches sont mises à jour immédiatement.
Marques et catégoriesLien vers cette section#
GET/api/v1/brands
GET/api/v1/categories
Les deux retournent toutes les lignes, triées par nom, sans pagination. Utilisez les identifiants avec les filtres brand et category sur GET /products.
curl https://tradecatalog.app/api/v1/categories \
-H "Authorization: Bearer $TRADECATALOG_API_KEY"
const res = await fetch('https://tradecatalog.app/api/v1/categories', {
headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: categories } = await res.json()
{
"data": [
{ "id": "a1b2c3d4-0000-4000-8000-000000000002", "name": "Bolts" },
{ "id": "a1b2c3d4-0000-4000-8000-000000000003", "name": "Hinges" }
]
}
Pour créer une marque ou une catégorie, donnez-lui un nom sur un produit dans PUT /products.