Passer au contenu principal

API de stock et d'entrepôt

Pour les développeurs : niveaux, articles, emplacements, corrections et ressources de l'entrepôt (réceptions, commandes fournisseur, transferts, inventaires) via l'API. Scopes, Idempotency-Key, curseurs et codes d'erreur.

H
Écrit par Hayko

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, exigent stock.write. Les réceptions exigent inbound.read et inbound.write. Les commandes fournisseur exigent purchasing.read et purchasing.write. Les listes de prélèvement exigent fulfilment.read et les tâches d'équipe tasks.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 unitPrice et currency que 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"
}

  • reserved est tout ce que les commandes ouvertes retiennent. available est sellable moins reserved.

  • lastSequence est le numéro du dernier mouvement de stock de cet article dans cet entrepôt. C'est le même numéro que sequence dans le webhook stock/adjusted.

  • sku doit 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, et locationQuantity contient 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 stockItemId ou sku. Un seul article doit correspondre.

  • Indiquez locationId. Un compte sans emplacements peut indiquer warehouseId à la place, ou rien pour l'entrepôt par défaut.

  • delta est la variation en unités : un nombre positif ajoute, un nombre négatif retire. Il ne peut pas être 0.

  • reasonCode est obligatoire : correction, found, damaged, expired, defective, lost, theft, sample ou other.

  • Pour un article géré par lots, indiquez lotCode et, 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 (filtres state, warehouseId, q), GET /api/v1/receipts/{id}, POST /api/v1/receipts (avec announce: true la réception est annoncée), PATCH /api/v1/receipts/{id}, POST .../{id}/lines, POST .../{id}/complete et POST .../{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}/close et POST .../{id}/cancel. L'envoi fonctionne exactement comme dans PortalX : avec email: 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 (429 RATE_LIMIT_EXCEEDED).

  • Articles et emplacements : POST et PATCH /api/v1/stock/items et /api/v1/stock/locations. Les emplacements exigent Full WMS avec des casiers. Les lignes de GET items et GET locations ont désormais version.

  • Lectures : GET /api/v1/stock/movements, GET /api/v1/picklists, GET /api/v1/counts (avec /{id} et /{id}/lines), GET /api/v1/containers et GET /api/v1/tasks (avec /{id}). Les tâches d'équipe sont en lecture seule.

  • La modification d'un enregistrement existant exige la version que 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/warehouses avec 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 (avec from et cursor).

  • Transferts (scope stock.read, WMS complet avec plusieurs entrepôts et les transferts) : GET /api/v1/transfers (filtres state, warehouseId, direction) et GET /api/v1/transfers/{id} avec les lignes et les détails shipping. Lecture seule.

  • Commandes fournisseur : la création et PATCH acceptent les champs d'en-tête reference, supplierReference, buyerUserId, internalNote, incoterm, incotermPlace, paymentTermsDays, shippingCost, shippingVatRate, pricesIncludeVat, deliveryAddress et partialDeliveryAllowed, et les lignes acceptent discountPct, vatRate, note, confirmedQty et confirmedOn. Les commandes renvoient les totaux (HT, TVA par taux, TTC) calculés par QrX. La liste filtre sur reference, expectedFrom et expectedTo. Les conditions sont figées une fois la commande envoyée.

  • Réceptions : la création et PATCH acceptent supplierId pour lier une réception à un fournisseur ; les lignes le renvoient.

  • Articles de stock et emplacements : les écritures d'emplacements acceptent zone ou zoneId et les champs de taille et de capacité ; les écritures d'articles acceptent storage, dangerousGoods, imageUrl et abcClassOverride. POST /api/v1/stock/adjust accepte 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: true et 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ête Idempotent-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_CONFLICT avec Retry-After. Un appel interrompu en cours de route libère sa clé après 15 minutes.

  • L'envoi d'une commande fournisseur avec email: true envoie 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 items et nextCursor. Renvoyez nextCursor comme cursor pour 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 que limit, voire aucune, alors que nextCursor est renseigné. Continuez à appeler jusqu'à ce que nextCursor soit 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 dans ownerCustomerId.

  • Utilisez partnerCustomerId dans la query pour garder un seul partenaire, et warehouseId pour l'un de ses entrepôts.

  • Les emplacements ne sont jamais renvoyés : locationId, GET locations et POST adjust ré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 pas stock.read ou stock.write.

  • 403 ACCESS_DENIED : le stock en entrepôt n'est pas activé pour le compte ou, avec scope=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 par stockItemId).

  • 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_MISSING ou TASKS_SCOPE_MISSING : la clé est restreinte et cette partie lui manque.

  • 404 INBOUND_NOT_FOUND, PURCHASE_ORDER_NOT_FOUND ou NOT_FOUND : l'enregistrement ne vous appartient pas.

  • 409 INBOUND_CONFLICT ou PURCHASING_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. 400 DG_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.

Avez-vous trouvé la réponse à votre question ?