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-devicesouvre 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 PKCES256est accepté, et la redirection esthttp://127.0.0.1:PORT/oauth/callbacksur 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/devicedepuis 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-Storeavec l'id de la boutique, saufGET /v1/me. Une boutique que le propriétaire n'a pas choisie répond403. - Chaque
POST,PATCHetDELETEporte uneIdempotency-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_grantet le heartbeat suivant410 device_revoked.
Les 19 scopes
La caisse demande toujours les 19, et DZBuild les accorde toujours tous.
| Scopes | Ce que la caisse peut faire |
|---|---|
openid, profile, offline_access | Savoir qui l'a reliée et rester reliée |
store:read | Lire les boutiques de la liaison, leur langue, leur plan et leur plafond de produits |
products:read, products:write | Lire les produits, les créer et les modifier par lots, ajouter des photos |
inventory:read, inventory:write | Lire et ajuster le stock des produits |
orders:read, orders:write | Recevoir les commandes de la boutique, en prendre une, la faire avancer, l'annuler |
customers:read | Vérifier un numéro de téléphone avant une vente |
pos:sales:read, pos:sales:write | Enregistrer ventes, retours et clôtures Z |
locations:read, locations:write | Enregistrer le magasin où se trouve la caisse |
backups:read, backups:write | Réservés : les sauvegardes ne sont pas proposées |
devices:self | Enregistrer la caisse, envoyer des heartbeats, se délier |
events:read | Lire le flux des changements |
Endpoints
| Méthode et chemin | Rôle |
|---|---|
GET /v1/me | Le propriétaire et les boutiques de la liaison |
GET /v1/store | La boutique choisie : nom, langue, devise DZD, moment de déduction du stock, plan et plafond de produits |
POST /v1/devices, GET /v1/devices | Enregistrer la caisse (une ligne par caisse et par boutique), lister les caisses |
POST /v1/devices/{id}/heartbeat | Toutes les 15 minutes : version, envois en attente et en échec |
DELETE /v1/devices/{id} | Délier cette caisse |
GET /v1/locations, POST /v1/locations | Le magasin où se trouve la caisse |
POST /v1/products/batch | Cré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}/images | Ticket photo, puis rattacher la photo envoyée |
POST /v1/inventory/adjustments/batch | Jusqu'à 500 fixations ou variations de stock |
POST /v1/pos/sales, POST /v1/pos/sales/{sale_id}/refunds, POST /v1/pos/closures | Ventes, 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}/cancel | Prendre une commande, la faire avancer, l'annuler |
GET /v1/customers?phone= | Les indicateurs d'un numéro de téléphone |
GET /v1/events | Le 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êmeskuet 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(createdouupdated) eterror: null. Un élément refusé porte un objeterroret niidnistatus. - 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 enactiveau-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
POST /v1/media/uploadsavecfilename,content_type(image/jpeg,image/pngouimage/webp),size(jusqu'à 8 Mio) etsha256. La réponse portemedia_idet uneupload_urlsignée valable 10 minutes.- La caisse envoie les octets bruts en
PUTsurupload_url, sans les en-têtes DZBuild. POST /v1/products/{id}/imagesavecmedia_idetpositionrattache la photo. La taille et le sha256 doivent correspondre au ticket, sinon l'appel répond422 media_mismatch. Un ticket inconnu ou expiré répond404 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 stocknot_tracked, un produit qui n'existe plusunknown_product. - La réponse liste chaque élément par
refavecstatusokouerror. - 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
201avec{"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_idest déjà enregistré répond409 already_existsavec l'id enregistré danserror.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=...etGET /v1/orders/{id}donnent les commandes de la boutique au format de la caisse, avec leurs articles.order_numberest le numéro court de la boutique quand il existe.POST /v1/orders/{id}/claimavecdevice_idetterminal: la première caisse l'emporte. La même caisse qui la reprend reçoit200; une autre caisse reçoit409 order_claimed.PATCH /v1/orders/{id}avecstatusdemande la prise (409 claim_requiredsinon). Passages permis : dependingouconfirmedversprocessing,shippedoudelivered, deprocessingversshippedoudelivered, et deshippedversdelivered. Tout autre passage répond409 transition_not_allowed.POST /v1/orders/{id}/cancelprendreason(out_of_stock,customer_unreachable,duplicateouother) et unenotefacultative 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épond409 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.updatedpour chaque commandepending,confirmedetprocessing. - Types :
order.created,order.updated,product.updated,product.deleted,inventory.level_changed,customer.updatedetdevice.revoked. Chaque événement a unidstable, ce qui permet à la caisse d'ignorer les doublons. inventory.level_changedporteold,new,deltaet unesource:orderquand une commande a fait bouger le stock (avec le numéro de commande),dashboardpour tout autre changement fait hors de la caisse, etposquand 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.revokedportedevice_iden 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
| Statut | code | Quand |
|---|---|---|
| 400 | bad_request | Un updated_since incorrect ou un curseur que cette API n'a pas émis |
| 401 | unauthorized | Jeton absent, faux ou expiré, ou liaison terminée |
| 402 | product_limit_reached | Un lot créerait un produit au-delà du plafond du plan |
| 403 | forbidden | Un chemin hors de la liste de la caisse, ou une boutique que le propriétaire n'a pas choisie |
| 404 | not_found | Produit, vente ou commande absent de cette boutique |
| 404 | device_not_found | Un id de caisse qui n'est pas cette caisse sur cette boutique |
| 404 | media_not_found | Ticket photo inconnu ou expiré |
| 409 | already_exists | Un document avec ce external_id est enregistré ; son id est dans details.id |
| 409 | order_claimed | Une autre caisse a pris la commande |
| 409 | claim_required | Faire avancer une commande que cette caisse n'a pas prise |
| 409 | transition_not_allowed | Le passage n'est pas permis depuis le statut de la commande |
| 410 | device_revoked | La caisse a été déconnectée |
| 413 | payload_too_large | Corps de plus de 1 Mio |
| 422 | validation_error | Un champ est incorrect ; details.field le nomme |
| 422 | media_mismatch | La photo envoyée ne correspond pas à son ticket |
| 422 | idempotency_key_reuse | Même Idempotency-Key avec un autre corps |
| 429 | rate_limited | Limite dépassée ; attendre Retry-After |
| 501 | not_implemented | Les chemins de sauvegarde |
| 503 | storage_unavailable | Le stockage des photos est injoignable ; réessayer plus tard |