Aller au contenu principal

DZBuild POS

DZBuild POS est la caisse Windows gratuite pour les magasins qui vendent aussi en ligne. Elle fonctionne hors ligne et, une fois reliée, reste synchronisée avec une ou plusieurs boutiques DZBuild. Cette page liste les appels d'une caisse reliée, pour que le support et les partenaires sachent ce qu'une caisse peut faire et ne peut pas faire.

Ces endpoints ne répondent qu'aux jetons délivrés à la caisse. Une clé API personnelle ou un jeton d'application reçoit 403 sur les chemins réservés au POS, et un jeton de caisse reçoit 403 sur tout chemin hors de sa liste.

À qui elle s'adresse​

  • Aux propriétaires qui vendent en magasin et sur leur boutique DZBuild. Seul le propriétaire de la boutique peut relier une caisse ; les membres de l'équipe ne le peuvent pas.
  • À tous les plans. Une caisse n'est pas une clé API : elle ne demande pas de plan Enterprise et ne prend aucune place de clé.
  • Dès que la première caisse s'enregistre, l'extension DZBuild POS apparaît dans le tableau de bord avec les caisses reliées, un bouton de déconnexion et les dernières ventes, retours et clôtures Z. https://dzbuild.com/dashboard/connected-devices ouvre cette page.

Comment une caisse se relie​

  • Découverte : GET https://dzbuild.com/.well-known/oauth-authorization-server (RFC 8414).
  • La caisse est un client OAuth 2.0 public, dzbuild-pos-windows, sans secret. Seul PKCE S256 est accepté, et la redirection est http://127.0.0.1:PORT/oauth/callback sur n'importe quel port.
  • Le propriétaire se connecte dans le navigateur du système et choisit les boutiques que la caisse peut utiliser, jusqu'à 10. Une caisse peut aussi se relier avec un code : elle en affiche un, et le propriétaire le saisit sur https://dzbuild.com/device depuis un téléphone.
  • Le jeton d'accès commence par dzpos_ et dure 15 minutes. Le jeton de rafraîchissement est remplacé à chaque utilisation et expire après 30 jours sans usage ou 180 jours au total. Renvoyer un ancien jeton de rafraîchissement met fin à la liaison.
  • Chaque appel porte X-DZ-Store avec l'id de la boutique, sauf GET /v1/me. Une boutique que le propriétaire n'a pas choisie répond 403.
  • Chaque POST, PATCH et DELETE porte une Idempotency-Key, avec les règles de Idempotence.
  • Limites : 120 requêtes par minute par caisse et 600 par boutique. Le quota API mensuel ne s'applique pas aux caisses.
  • Déconnecter la caisse dans le tableau de bord met fin à la liaison : le rafraîchissement suivant répond 400 invalid_grant et le heartbeat suivant 410 device_revoked.

Les 19 scopes​

La caisse demande toujours les 19, et DZBuild les accorde toujours tous.

ScopesCe que la caisse peut faire
openid, profile, offline_accessSavoir qui l'a reliée et rester reliée
store:readLire les boutiques de la liaison, leur langue, leur plan et leur plafond de produits
products:read, products:writeLire les produits, les créer et les modifier par lots, ajouter des photos
inventory:read, inventory:writeLire et ajuster le stock des produits
orders:read, orders:writeRecevoir les commandes de la boutique, en prendre une, la faire avancer, l'annuler
customers:readVérifier un numéro de téléphone avant une vente
pos:sales:read, pos:sales:writeEnregistrer ventes, retours et clôtures Z
locations:read, locations:writeEnregistrer le magasin où se trouve la caisse
backups:read, backups:writeRéservés : les sauvegardes ne sont pas proposées
devices:selfEnregistrer la caisse, envoyer des heartbeats, se délier
events:readLire le flux des changements

Endpoints​

Méthode et cheminRôle
GET /v1/meLe propriétaire et les boutiques de la liaison
GET /v1/storeLa boutique choisie : nom, langue, devise DZD, moment de déduction du stock, plan et plafond de produits
POST /v1/devices, GET /v1/devicesEnregistrer la caisse (une ligne par caisse et par boutique), lister les caisses
POST /v1/devices/{id}/heartbeatToutes les 15 minutes : version, envois en attente et en échec
DELETE /v1/devices/{id}Délier cette caisse
GET /v1/locations, POST /v1/locationsLe magasin où se trouve la caisse
POST /v1/products/batchCréer ou modifier jusqu'à 100 produits
GET /v1/products, GET /v1/products/{id}Produits modifiés depuis une date, un produit
POST /v1/media/uploads, POST /v1/products/{id}/imagesTicket photo, puis rattacher la photo envoyée
POST /v1/inventory/adjustments/batchJusqu'à 500 fixations ou variations de stock
POST /v1/pos/sales, POST /v1/pos/sales/{sale_id}/refunds, POST /v1/pos/closuresVentes, retours, clôtures Z
GET /v1/orders, GET /v1/orders/{id}Les commandes de la boutique au format de la caisse
POST /v1/orders/{id}/claim, PATCH /v1/orders/{id}, POST /v1/orders/{id}/cancelPrendre une commande, la faire avancer, l'annuler
GET /v1/customers?phone=Les indicateurs d'un numéro de téléphone
GET /v1/eventsLe flux des changements

Certains chemins sont partagés avec les clés API (produits, commandes, clients). Une caisse reçoit les formats de cette page ; une clé API garde les formats de la section Ressources.

Lot de produits​

POST /v1/products/batch prend items, jusqu'à 100. Chaque élément porte le external_id propre à la caisse, name, un sku et un barcode facultatifs, pricing.price en chaîne à deux décimales ("4500.00"), inventory.track_stock, status (active, draft ou archived) et une category facultative avec son propre external_id et son name.

  • Un élément modifie le produit déjà relié à son external_id. Sinon, il reprend un produit sans variantes qui a le même sku et aucune liaison. Sinon, il crée un produit avec 0 en stock.
  • Une catégorie est retrouvée par son external_id, ou créée à partir de son nom.
  • La réponse liste chaque élément : external_id, id, status (created ou updated) et error: null. Un élément refusé porte un objet error et ni id ni status.
  • Plafond du plan : quand la boutique a déjà autant de produits actifs que son plan le permet, un lot qui créerait un produit, brouillon ou non, répond 402 product_limit_reached. Les éléments placés avant restent écrits. Passer un produit existant en active au-delà du plafond est une erreur sur cet élément seulement.
  • Le lot ne fixe jamais le stock. Le stock passe par l'appel d'ajustements.

Photos​

  1. POST /v1/media/uploads avec filename, content_type (image/jpeg, image/png ou image/webp), size (jusqu'à 8 Mio) et sha256. La réponse porte media_id et une upload_url signée valable 10 minutes.
  2. La caisse envoie les octets bruts en PUT sur upload_url, sans les en-têtes DZBuild.
  3. POST /v1/products/{id}/images avec media_id et position rattache la photo. La taille et le sha256 doivent correspondre au ticket, sinon l'appel répond 422 media_mismatch. Un ticket inconnu ou expiré répond 404 media_not_found. Un produit garde jusqu'à 20 photos.

Stock​

POST /v1/inventory/adjustments/batch prend jusqu'à 500 éléments. Chaque élément indique product_external_id, target: "product", soit set (pièces entières) soit delta, une reason (pos_sale, pos_return, restock, count ou loss) et une ref unique.

  • Seuls les produits qui suivent le stock au niveau du produit peuvent être ajustés. Un produit avec un stock par variante répond l'erreur d'élément variant_product, un produit sans suivi de stock not_tracked, un produit qui n'existe plus unknown_product.
  • La réponse liste chaque élément par ref avec status ok ou error.
  • Les changements de stock de la caisse n'apparaissent jamais dans l'historique des modifications du tableau de bord et ne proposent jamais d'annulation.

Documents POS​

POST /v1/pos/sales enregistre un ticket, une facture ou un bon de livraison ; POST /v1/pos/sales/{sale_id}/refunds un bon de retour ou un avoir sur cette vente ; POST /v1/pos/closures une clôture Z avec ses totaux et ses ancres d'empreinte.

  • Les documents sont gardés tels qu'envoyés et ne changent jamais : il n'existe aucun chemin de modification ou de suppression.
  • Une vente répond 201 avec {"id": 99120, "stock_applied": false}. Le stock passe par l'appel d'ajustements, jamais par un document.
  • Les documents POS sont séparés des commandes. Ils ne comptent pas dans la limite mensuelle de commandes de la boutique, ne notifient personne et n'atteignent jamais Google Sheets ni les webhooks.
  • Les montants sont des chaînes à deux décimales, les quantités des nombres à 3 décimales au plus, avec jusqu'à 500 lignes et 20 paiements par document.
  • Envoyer un document dont le external_id est déjà enregistré répond 409 already_exists avec l'id enregistré dans error.details.id. La caisse le lit comme un succès.
  • Un retour sur une vente d'une autre boutique répond 404 not_found.

Commandes​

  • GET /v1/orders?updated_since=... et GET /v1/orders/{id} donnent les commandes de la boutique au format de la caisse, avec leurs articles. order_number est le numéro court de la boutique quand il existe.
  • POST /v1/orders/{id}/claim avec device_id et terminal : la première caisse l'emporte. La même caisse qui la reprend reçoit 200 ; une autre caisse reçoit 409 order_claimed.
  • PATCH /v1/orders/{id} avec status demande la prise (409 claim_required sinon). Passages permis : de pending ou confirmed vers processing, shipped ou delivered, de processing vers shipped ou delivered, et de shipped vers delivered. Tout autre passage répond 409 transition_not_allowed.
  • POST /v1/orders/{id}/cancel prend reason (out_of_stock, customer_unreachable, duplicate ou other) et une note facultative de 500 caractères au plus. Le stock revient selon les règles de stock de la boutique. Si une autre caisse tient la prise, l'annulation répond 409 order_claimed.
  • Un changement de statut depuis la caisse déclenche les mêmes suites qu'un changement fait dans le tableau de bord, notifications et mise à jour Google Sheets comprises.

Clients​

GET /v1/customers?phone=0550123456 répond une page d'un élément au plus : id, is_banned et fraud_score. Elle ne regarde que les clients de la boutique, accepte le numéro avec ou sans +213, et ne donne ni nom, ni adresse, ni historique. Un numéro inconnu donne une page vide.

Événements​

GET /v1/events?wait=25&limit=200&cursor=... renvoie items, next_cursor et has_more. next_cursor est toujours présent, même sur une page vide ; la caisse le renvoie à l'appel suivant.

  • La périphérie garde l'appel jusqu'à 25 secondes et répond dès qu'un changement arrive.
  • Le premier appel, sans curseur, commence par un order.updated pour chaque commande pending, confirmed et processing.
  • Types : order.created, order.updated, product.updated, product.deleted, inventory.level_changed, customer.updated et device.revoked. Chaque événement a un id stable, ce qui permet à la caisse d'ignorer les doublons.
  • inventory.level_changed porte old, new, delta et une source : order quand une commande a fait bouger le stock (avec le numéro de commande), dashboard pour tout autre changement fait hors de la caisse, et pos quand une autre caisse de la même boutique l'a changé (la caisse ignore cette source).
  • Les écritures de produits et de stock de la caisse elle-même ne lui sont pas renvoyées.
  • device.revoked porte device_id en chaîne, une seule fois, après la déconnexion de la caisse.
  • Un curseur que cette API n'a pas émis répond 400 bad_request.

Sauvegardes​

DZBuild ne garde pas les sauvegardes des caisses. GET /v1/backups, POST /v1/backups, POST /v1/backups/{id}/complete et GET /v1/backups/{id}/download répondent toujours 501 not_implemented, et la caisse garde ses sauvegardes sur le PC.

Codes d'erreur​

StatutcodeQuand
400bad_requestUn updated_since incorrect ou un curseur que cette API n'a pas émis
401unauthorizedJeton absent, faux ou expiré, ou liaison terminée
402product_limit_reachedUn lot créerait un produit au-delà du plafond du plan
403forbiddenUn chemin hors de la liste de la caisse, ou une boutique que le propriétaire n'a pas choisie
404not_foundProduit, vente ou commande absent de cette boutique
404device_not_foundUn id de caisse qui n'est pas cette caisse sur cette boutique
404media_not_foundTicket photo inconnu ou expiré
409already_existsUn document avec ce external_id est enregistré ; son id est dans details.id
409order_claimedUne autre caisse a pris la commande
409claim_requiredFaire avancer une commande que cette caisse n'a pas prise
409transition_not_allowedLe passage n'est pas permis depuis le statut de la commande
410device_revokedLa caisse a été déconnectée
413payload_too_largeCorps de plus de 1 Mio
422validation_errorUn champ est incorrect ; details.field le nomme
422media_mismatchLa photo envoyée ne correspond pas à son ticket
422idempotency_key_reuseMême Idempotency-Key avec un autre corps
429rate_limitedLimite dépassée ; attendre Retry-After
501not_implementedLes chemins de sauvegarde
503storage_unavailableLe stockage des photos est injoignable ; réessayer plus tard
Cette page pour les outils IAVoir en MarkdownOuvrir dans ChatGPTOuvrir dans Claude