Skip to main content

DZBuild POS

DZBuild POS is the free Windows till for shops that also sell online. It keeps working offline and, once linked, stays in sync with one or more DZBuild stores. This page lists the calls a linked till makes, so support staff and partners can see what a till can and cannot do.

These endpoints answer only tokens issued to the till. A personal API key or an app token gets 403 on the POS-only paths, and a till token gets 403 on every path outside its list.

Who it is for​

  • Shop owners who sell in a physical shop and on their DZBuild store. Only the store owner can link a till; team members cannot.
  • Every plan. A till is not an API key, so it needs no Enterprise plan and takes no key slot.
  • Once the first till registers, the DZBuild POS extension appears in the dashboard with the linked tills, a disconnect button and the latest sales, refunds and Z closures. https://dzbuild.com/dashboard/connected-devices opens that page.
  • Discovery: GET https://dzbuild.com/.well-known/oauth-authorization-server (RFC 8414).
  • The till is a public OAuth 2.0 client, dzbuild-pos-windows, with no secret. Only PKCE S256 is accepted, and the redirect is http://127.0.0.1:PORT/oauth/callback on any port.
  • The owner signs in in the system browser and picks the stores the till may use, up to 10. A till can also link with a code: it shows one, and the owner types it at https://dzbuild.com/device from a phone.
  • The access token starts with dzpos_ and lasts 15 minutes. The refresh token is replaced on every use and ends after 30 days without use or 180 days in all. Sending an old refresh token again ends the link.
  • Every call carries X-DZ-Store with the store id, except GET /v1/me. A store the owner did not pick answers 403.
  • Every POST, PATCH and DELETE carries an Idempotency-Key, with the rules in Idempotency.
  • Limits: 120 requests a minute per till and 600 per store. The monthly API quota does not apply to tills.
  • Disconnecting the till in the dashboard ends the link: the next refresh answers 400 invalid_grant and the next heartbeat 410 device_revoked.

The 19 scopes​

The till always asks for all 19, and DZBuild always grants all 19.

ScopesWhat the till can do
openid, profile, offline_accessKnow who linked it and stay linked
store:readRead the stores of the link, their language, plan and product cap
products:read, products:writeRead products, create and update them in batches, add photos
inventory:read, inventory:writeRead and adjust product stock
orders:read, orders:writeReceive the store's orders, claim one, move it, cancel it
customers:readCheck one phone number before a sale
pos:sales:read, pos:sales:writeRecord sales, refunds and Z closures
locations:read, locations:writeRegister the shop the till sits in
backups:read, backups:writeReserved: backups are not offered
devices:selfRegister the till, send heartbeats, unlink
events:readRead the change feed

Endpoints​

Method and pathWhat it does
GET /v1/meThe owner and the stores of the link
GET /v1/storeThe chosen store: name, language, currency DZD, stock timing, plan and product cap
POST /v1/devices, GET /v1/devicesRegister the till (one row per till and store), list the tills
POST /v1/devices/{id}/heartbeatEvery 15 minutes: version, pending and failed uploads
DELETE /v1/devices/{id}Unlink this till
GET /v1/locations, POST /v1/locationsThe shop the till sits in
POST /v1/products/batchCreate or update up to 100 products
GET /v1/products, GET /v1/products/{id}Products changed since a date, one product
POST /v1/media/uploads, POST /v1/products/{id}/imagesPhoto ticket, then attach the uploaded photo
POST /v1/inventory/adjustments/batchUp to 500 stock sets or changes
POST /v1/pos/sales, POST /v1/pos/sales/{sale_id}/refunds, POST /v1/pos/closuresSales, refunds, Z closures
GET /v1/orders, GET /v1/orders/{id}The store's orders in the till's shape
POST /v1/orders/{id}/claim, PATCH /v1/orders/{id}, POST /v1/orders/{id}/cancelTake an order, move it, cancel it
GET /v1/customers?phone=Flags for one phone number
GET /v1/eventsThe change feed

Some paths are shared with API keys (products, orders, customers). A till gets the shapes on this page; an API key keeps the shapes in Resources.

Products batch​

POST /v1/products/batch takes items, up to 100. Each item has the till's own external_id, name, an optional sku and barcode, pricing.price as a string with two decimals ("4500.00"), inventory.track_stock, status (active, draft or archived) and an optional category with its own external_id and name.

  • An item updates the product already linked to its external_id. Failing that, it adopts a product without variants that has the same sku and no link yet. Failing that, it creates a product with 0 in stock.
  • A category is found by its external_id, or created from its name.
  • The answer lists every item: external_id, id, status (created or updated) and error: null. A refused item has an error object and no id or status.
  • Plan cap: when the store already has as many active products as its plan allows, a batch that would create a product, draft or not, answers 402 product_limit_reached. Items before that one stay written. Switching an existing product to active past the cap is an error on that item only.
  • The batch never sets stock. Stock goes through the adjustments call.

Photos​

  1. POST /v1/media/uploads with filename, content_type (image/jpeg, image/png or image/webp), size (up to 8 MiB) and sha256. The answer carries media_id and a signed upload_url that works for 10 minutes.
  2. The till sends the raw bytes with PUT to upload_url, without the DZBuild headers.
  3. POST /v1/products/{id}/images with media_id and position attaches the photo. The size and the sha256 must match the ticket, or the call answers 422 media_mismatch. An unknown or expired ticket answers 404 media_not_found. A product holds up to 20 photos.

Stock​

POST /v1/inventory/adjustments/batch takes up to 500 items. Each item names product_external_id, target: "product", either set (whole pieces) or delta, a reason (pos_sale, pos_return, restock, count or loss) and a unique ref.

  • Only products that track stock at the product level can be adjusted. A product with stock per variant answers the item error variant_product, one that does not track stock not_tracked, a product that no longer exists unknown_product.
  • The answer lists every item by ref with status ok or error.
  • Till stock changes never appear in the dashboard's change history and never offer an undo.

POS documents​

POST /v1/pos/sales records a ticket, an invoice or a delivery note; POST /v1/pos/sales/{sale_id}/refunds a return note or a credit note against that sale; POST /v1/pos/closures a Z closure with its totals and hash anchors.

  • Documents are kept as sent and never change: there is no update or delete path.
  • A sale answers 201 with {"id": 99120, "stock_applied": false}. Stock moves through the adjustments call, never through a document.
  • POS documents live apart from orders. They do not count toward the store's monthly order limit, do not notify anyone, and never reach Google Sheets or webhooks.
  • Money is a string with two decimals, quantities a number with up to 3 decimals, up to 500 lines and 20 payments per document.
  • Sending a document whose external_id is already recorded answers 409 already_exists with the recorded id in error.details.id. The till reads that as success.
  • A refund for a sale of another store answers 404 not_found.

Orders​

  • GET /v1/orders?updated_since=... and GET /v1/orders/{id} give the store's orders in the till's shape, with their items. order_number is the store's short number when it has one.
  • POST /v1/orders/{id}/claim with device_id and terminal: the first till wins. The same till claiming again gets 200; another till gets 409 order_claimed.
  • PATCH /v1/orders/{id} with status needs the claim (409 claim_required otherwise). Allowed moves: from pending or confirmed to processing, shipped or delivered, from processing to shipped or delivered, and from shipped to delivered. Any other move answers 409 transition_not_allowed.
  • POST /v1/orders/{id}/cancel takes reason (out_of_stock, customer_unreachable, duplicate or other) and an optional note up to 500 characters. Stock comes back under the store's own stock rules. If another till holds the claim, cancel answers 409 order_claimed.
  • A status change from the till runs the same follow-up as one made in the dashboard, notifications and the Google Sheets update included.

Customers​

GET /v1/customers?phone=0550123456 answers a page with at most one item: id, is_banned and fraud_score. It looks only at the store's own customers, accepts the phone with or without +213, and gives no name, address or history. An unknown number gives an empty page.

Events​

GET /v1/events?wait=25&limit=200&cursor=... returns items, next_cursor and has_more. next_cursor is always present, also on an empty page; the till sends it back on the next call.

  • The edge holds the call up to 25 seconds and answers as soon as something changed.
  • The first call, without a cursor, starts with order.updated for every pending, confirmed and processing order.
  • Types: order.created, order.updated, product.updated, product.deleted, inventory.level_changed, customer.updated and device.revoked. Every event has a stable id, so the till can ignore repeats.
  • inventory.level_changed carries old, new, delta and a source: order when an order moved the stock (with the order number), dashboard for any other change made outside the till, and pos when another till of the same store changed it (the till ignores that source).
  • The till's own product and stock writes are not sent back to it.
  • device.revoked carries device_id as a string, once, after the till is disconnected.
  • A cursor this API did not issue answers 400 bad_request.

Backups​

DZBuild does not store till backups. GET /v1/backups, POST /v1/backups, POST /v1/backups/{id}/complete and GET /v1/backups/{id}/download always answer 501 not_implemented, and the till keeps its backups on the PC.

Error codes​

StatuscodeWhen
400bad_requestA wrong updated_since or a cursor this API did not issue
401unauthorizedToken missing, wrong or expired, or the link was ended
402product_limit_reachedA batch would create a product past the plan's cap
403forbiddenA path outside the till's list, or a store the owner did not pick
404not_foundProduct, sale or order not on this store
404device_not_foundA till id that is not this till on this store
404media_not_foundPhoto ticket unknown or expired
409already_existsA document with this external_id is recorded; its id is in details.id
409order_claimedAnother till claimed the order
409claim_requiredMoving an order this till has not claimed
409transition_not_allowedThe move is not allowed from the order's status
410device_revokedThe till was disconnected
413payload_too_largeBody larger than 1 MiB
422validation_errorA field is wrong; details.field names it
422media_mismatchThe uploaded photo does not match its ticket
422idempotency_key_reuseSame Idempotency-Key with a different body
429rate_limitedOver a limit; wait for Retry-After
501not_implementedThe backup paths
503storage_unavailablePhoto storage is not reachable; retry later
This page for AI toolsView as MarkdownOpen in ChatGPTOpen in Claude