API de stock et d'entrepôt
Avec l'API de stock, votre propre système lit et modifie votre entrepôt QrX : niveaux de stock, articles, emplacements, mouvements, réceptions, commandes fournisseur, listes de prélèvement, inventaires, conteneurs et tâches d'équipe. Cet article s'adresse aux développeurs. L'URL de base est https://api.qr-x.net et chaque appel demande Authorization: Bearer <accessToken> ; voir "Démarrage rapide pour les développeurs : API publique QrX et webhooks" pour le jeton. La référence complète se trouve sur qrx.eu/api. Aucun prix, coût ou valeur de stock n'est jamais renvoyé.
Accès et scopes
Les niveaux de stock, articles, emplacements, mouvements, inventaires et conteneurs exigent
stock.read; créer ou modifier des articles et des emplacements, et enregistrer une correction, exigentstock.write. Les réceptions exigentinbound.readetinbound.write. Les commandes fournisseur exigentpurchasing.readetpurchasing.write. Les listes de prélèvement exigentfulfilment.readet les tâches d'équipetasks.read.Une clé restreinte ne reçoit jamais les nouvelles parties d'elle-même. Cochez Achats, Traitement ou Tâches dans Accès API si la clé en a besoin. Les lignes de réception n'affichent
unitPriceetcurrencyque si la clé peut aussi lire les commandes fournisseur.Une clé API qui n'est pas restreinte a un accès complet et peut donc utiliser l'API de stock tout de suite.
Pour limiter une clé, ouvrez Settings > Business > Merchant Channels, ouvrez le canal de vente, onglet Settings, bloc Accès API, et cochez Lire les niveaux de stock, les articles, les emplacements, les mouvements, les inventaires et les conteneurs et Ajuster le stock et créer ou modifier des articles et des emplacements selon le besoin. Voir "Restreindre une clé API (Accès API)".
Le stock en entrepôt de QrX doit être activé sur votre compte. Sinon l'API répond 403
ACCESS_DENIED.
GET /api/v1/stock/levels
Une ligne par article de stock et par entrepôt. Query : stockItemId, sku, warehouseId, locationId, updatedSince (ISO 8601), cursor, limit (1 à 200, 50 par défaut) et scope.
{
"items": [{
"stockItemId": 345, "sku": "TSHIRT-M", "ean": "8712345678906",
"ownerCustomerId": null, "partnerCustomerId": null,
"warehouseId": 1, "warehouseCode": "MAIN",
"onHand": 42, "sellable": 40, "blocked": 2, "reserved": 5,
"incoming": 0, "backorder": 0, "available": 35,
"locationQuantity": null,
"lastSequence": 98231, "updatedAt": "2026-10-11T09:12:03Z"
}],
"nextCursor": "1-345"
}
reservedest tout ce que les commandes ouvertes retiennent.availableestsellablemoinsreserved.lastSequenceest le numéro du dernier mouvement de stock de cet article dans cet entrepôt. C'est le même numéro quesequencedans le webhookstock/adjusted.skudoit correspondre exactement ; la casse et les espaces en début et en fin sont ignorés.Avec
locationId, vous n'obtenez que les articles qui ont du stock à cet emplacement, etlocationQuantitycontient les unités qui s'y trouvent.
GET /api/v1/stock/items
Vos articles de stock, les plus récents en premier. Query : sku, ean, updatedSince, cursor, limit (1 à 200, 50 par défaut) et scope.
{
"items": [{
"id": 345, "sku": "TSHIRT-M", "name": "T-shirt M", "ean": "8712345678906",
"unit": "piece", "kind": "simple", "trackingMode": "none", "isActive": true,
"ownerCustomerId": null, "partnerCustomerId": null, "weightG": 180,
"updatedAt": "2026-10-01T08:00:00Z"
}],
"nextCursor": 344
}
GET /api/v1/stock/locations
Les emplacements qui peuvent contenir du stock, les plus récents en premier. Query : warehouseId, cursor, limit (1 à 200, 50 par défaut).
{
"items": [{ "id": 77, "warehouseId": 1, "warehouseCode": "MAIN", "code": "A-01-02",
"kind": "bin", "zone": "A", "isActive": true }],
"nextCursor": 76
}
POST /api/v1/stock/adjust
Enregistre une correction de stock. L'en-tête Idempotency-Key est obligatoire.
POST /api/v1/stock/adjust
Idempotency-Key: erp-count-2026-10-11-0001{ "stockItemId": 345, "sku": null, "warehouseId": null, "locationId": 77, "delta": -3,
"reasonCode": "correction", "note": "Cycle count by ERP", "lotCode": null, "expiryDate": null }
Indiquez
stockItemIdousku. Un seul article doit correspondre.Indiquez
locationId. Un compte sans emplacements peut indiquerwarehouseIdà la place, ou rien pour l'entrepôt par défaut.deltaest la variation en unités : un nombre positif ajoute, un nombre négatif retire. Il ne peut pas être 0.reasonCodeest obligatoire :correction,found,damaged,expired,defective,lost,theft,sampleouother.Pour un article géré par lots, indiquez
lotCodeet, lorsque l'article en a besoin,expiryDate.
La réponse contient le nouveau niveau de l'article dans cet entrepôt :
{ "movementId": 98231, "stockItemId": 345, "warehouseId": 1, "locationId": 77, "delta": -3,
"onHand": 42, "sellable": 40, "available": 35, "sequence": 98231, "replayed": false }
Ressources de l'entrepôt
Toutes les listes utilisent cursor et limit (1 à 100, 50 par défaut) et répondent avec items et nextCursor, du plus récent au plus ancien. Ces ressources ne concernent que les données de votre propre entrepôt : scope=partner répond 422. Tous les champs des requêtes et des réponses figurent dans la référence sur qrx.eu/api.
Réceptions :
GET /api/v1/receipts(filtresstate,warehouseId,q),GET /api/v1/receipts/{id},POST /api/v1/receipts(avecannounce: truela réception est annoncée),PATCH /api/v1/receipts/{id},POST .../{id}/lines,POST .../{id}/completeetPOST .../{id}/cancel.Commandes fournisseur :
GET /api/v1/purchase-orders,GET /api/v1/purchase-orders/{id},POST /api/v1/purchase-orders(un brouillon),PATCH .../{id},POST .../{id}/lines,POST .../{id}/send,POST .../{id}/closeetPOST .../{id}/cancel. L'envoi fonctionne exactement comme dans PortalX : avecemail: true, QrX envoie le PDF par e-mail au fournisseur (ou àto), au maximum 5 fois par commande et 30 fois par compte et par heure (429RATE_LIMIT_EXCEEDED).Articles et emplacements :
POSTetPATCH /api/v1/stock/itemset/api/v1/stock/locations. Les emplacements exigent Full WMS avec des casiers. Les lignes deGET itemsetGET locationsont désormaisversion.Lectures :
GET /api/v1/stock/movements,GET /api/v1/picklists,GET /api/v1/counts(avec/{id}et/{id}/lines),GET /api/v1/containersetGET /api/v1/tasks(avec/{id}). Les tâches d'équipe sont en lecture seule.La modification d'un enregistrement existant exige la
versionque vous avez lue en dernier. Si quelqu'un a modifié l'enregistrement entre-temps, vous recevez 409 : relisez-le et réessayez.Entrepôts (scope
stock.read, WMS complet) :GET /api/v1/warehousesavec le code, le nom, l'entrepôt par défaut, le fuseau horaire, l'adresse, le contact, les instructions de réception, les quais de chargement et le GLN ;GET /api/v1/warehouses/{id},.../{id}/hours(horaires de réception et d'expédition par jour de la semaine) et.../{id}/closures(avecfrometcursor).Transferts (scope
stock.read, WMS complet avec plusieurs entrepôts et les transferts) :GET /api/v1/transfers(filtresstate,warehouseId,direction) etGET /api/v1/transfers/{id}avec les lignes et les détailsshipping. Lecture seule.Commandes fournisseur : la création et
PATCHacceptent les champs d'en-têtereference,supplierReference,buyerUserId,internalNote,incoterm,incotermPlace,paymentTermsDays,shippingCost,shippingVatRate,pricesIncludeVat,deliveryAddressetpartialDeliveryAllowed, et les lignes acceptentdiscountPct,vatRate,note,confirmedQtyetconfirmedOn. Les commandes renvoient les totaux (HT, TVA par taux, TTC) calculés par QrX. La liste filtre surreference,expectedFrometexpectedTo. Les conditions sont figées une fois la commande envoyée.Réceptions : la création et
PATCHacceptentsupplierIdpour lier une réception à un fournisseur ; les lignes le renvoient.Articles de stock et emplacements : les écritures d'emplacements acceptent
zoneouzoneIdet les champs de taille et de capacité ; les écritures d'articles acceptentstorage,dangerousGoods,imageUrletabcClassOverride.POST /api/v1/stock/adjustaccepte vos propres codes motif.
Idempotency-Key
Envoyez votre propre texte unique par correction. Si vous renvoyez la même requête avec la même clé, vous recevez de nouveau la première réponse avec
replayed: trueet rien n'est enregistré deux fois.La même clé avec un autre corps est refusée avec 409
DUPLICATE_IDEMPOTENCY_KEY.Une clé ne peut pas commencer par un préfixe système QrX tel que
mcp-ni se terminer par deux-points suivis de chiffres (422).Chaque POST et PATCH des ressources de l'entrepôt exige l'en-tête
Idempotency-Key(1 à 140 caractères). La même clé avec le même corps sur la même route renvoie la première réponse, avec l'en-têteIdempotent-Replayed: true. Une création rejouée d'une réception ou d'une commande fournisseur renvoie l'enregistrement tel qu'il est maintenant.Les réponses sont conservées 30 jours. Un appel en échec (4xx ou 5xx) n'est pas conservé : vous pouvez renvoyer la même clé.
Tant que le premier appel avec une clé est en cours, vous recevez 409
CONCURRENCY_CONFLICTavecRetry-After. Un appel interrompu en cours de route libère sa clé après 15 minutes.L'envoi d'une commande fournisseur avec
email: trueenvoie l'e-mail au fournisseur au maximum une fois par clé. Si un appel échoue après l'envoi de la commande mais avant l'e-mail, aucun e-mail ne suit pour cette clé : vérifiez l'historique des e-mails de la commande dans PortalX et renvoyez avec une nouvelle clé.
Curseurs et updatedSince
Chaque liste renvoie
itemsetnextCursor. RenvoyeznextCursorcommecursorpour la page suivante. Null signifie la fin. Il n'y a pas de total.Traitez un curseur comme une valeur opaque.
Avec
updatedSince, vous n'obtenez que les lignes modifiées à cette heure ou après. Le serveur examine au plus 1 000 lignes par appel, une page peut donc contenir moins de lignes quelimit, voire aucune, alors quenextCursorest renseigné. Continuez à appeler jusqu'à ce quenextCursorsoit null.
Stock chez un partenaire logistique : scope=partner
Lorsqu'un partenaire logistique détient votre stock, ajoutez scope=partner à GET levels et GET items pour lire votre propre stock chez vos partenaires. Sans ce paramètre, vous obtenez la valeur par défaut, scope=warehouse : le stock de vos propres entrepôts.
Chaque ligne contient
partnerCustomerId(l'entrepôt partenaire) et votre propre id dansownerCustomerId.Utilisez
partnerCustomerIddans la query pour garder un seul partenaire, etwarehouseIdpour l'un de ses entrepôts.Les emplacements ne sont jamais renvoyés :
locationId,GET locationsetPOST adjustrépondent 422 dans ce scope.Il vous faut au moins un partenaire avec un accès actif ; le stock en entrepôt n'a pas besoin d'être activé sur votre propre compte.
Codes d'erreur
401
UNAUTHORIZED: pas de jeton d'accès valide.403
STOCK_SCOPE_MISSING: la clé est restreinte et n'a passtock.readoustock.write.403
ACCESS_DENIED: le stock en entrepôt n'est pas activé pour le compte ou, avecscope=partner, aucun partenaire n'a d'accès actif.404
STOCK_NOT_FOUND: l'article, l'entrepôt ou l'emplacement n'est pas le vôtre.400
IDEMPOTENCY_KEY_REQUIRED: un POST sans l'en-tête.422
VALIDATION_FAILED: saisie invalide, par exemple delta 0, un code de motif inconnu, un curseur invalide, ou plus de 50 articles avec le même SKU ou EAN (filtrez parstockItemId).409
DUPLICATE_IDEMPOTENCY_KEY: la même clé avec un autre corps.409
STOCK_CONFLICT: pas assez de stock, du stock affecté à une liste de prélèvement, un SKU qui correspond à plusieurs articles, ou un article inactif.409
CONCURRENCY_CONFLICT: renvoyez la même requête avec la même clé.429 : trop de requêtes ; attendez
Retry-After.403
INBOUND_SCOPE_MISSING,PURCHASING_SCOPE_MISSING,FULFILMENT_SCOPE_MISSINGouTASKS_SCOPE_MISSING: la clé est restreinte et cette partie lui manque.404
INBOUND_NOT_FOUND,PURCHASE_ORDER_NOT_FOUNDouNOT_FOUND: l'enregistrement ne vous appartient pas.409
INBOUND_CONFLICTouPURCHASING_CONFLICT: la version ou l'état ne permet pas la modification.429
RATE_LIMIT_EXCEEDED: trop d'e-mails au fournisseur pour une commande fournisseur.400
ZONE_UNKNOWN: la zone n'est pas une zone active de cet entrepôt. 400DG_CLASS_UNKNOWN: la classe de marchandises dangereuses n'est pas une classe visible de votre compte. Un motif qui exige des photos ne peut pas être utilisé via l'API.
Quand contacter notre équipe support
Contactez le support QrX lorsque l'API répond ACCESS_DENIED alors que le stock en entrepôt est activé, ou lorsqu'un niveau diffère de PortalX. Indiquez le requestId de la réponse, le SKU et l'heure. N'envoyez jamais la clé API ni le jeton.
