Skip to main content

DZBuild POS

How the free DZBuild POS Windows till links to a store, and the calls it makes for products, photos, stock, sales documents, orders, customers and the events feed.

Written by Support

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.

How a till links

  • 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.

Scopes

What the till can do

openid, profile, offline_access

Know who linked it and stay linked

store:read

Read the stores of the link, their language, plan and product cap

products:read, products:write

Read products, create and update them in batches, add photos

inventory:read, inventory:write

Read and adjust product stock

orders:read, orders:write

Receive the store's orders, claim one, move it, cancel it

customers:read

Check one phone number before a sale

pos:sales:read, pos:sales:write

Record sales, refunds and Z closures

locations:read, locations:write

Register the shop the till sits in

backups:read, backups:write

Reserved: backups are not offered

devices:self

Register the till, send heartbeats, unlink

events:read

Read the change feed

Endpoints

Method and path

What it does

GET /v1/me

The owner and the stores of the link

GET /v1/store

The chosen store: name, language, currency DZD, stock timing, plan and product cap

POST /v1/devices, GET /v1/devices

Register the till (one row per till and store), list the tills

POST /v1/devices/{id}/heartbeat

Every 15 minutes: version, pending and failed uploads

DELETE /v1/devices/{id}

Unlink this till

GET /v1/locations, POST /v1/locations

The shop the till sits in

POST /v1/products/batch

Create 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}/images

Photo ticket, then attach the uploaded photo

POST /v1/inventory/adjustments/batch

Up to 500 stock sets or changes

POST /v1/pos/sales, POST /v1/pos/sales/{sale_id}/refunds, POST /v1/pos/closures

Sales, 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}/cancel

Take an order, move it, cancel it

GET /v1/customers?phone=

Flags for one phone number

GET /v1/events

The 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

Status

code

When

400

bad_request

A wrong updated_since or a cursor this API did not issue

401

unauthorized

Token missing, wrong or expired, or the link was ended

402

product_limit_reached

A batch would create a product past the plan's cap

403

forbidden

A path outside the till's list, or a store the owner did not pick

404

not_found

Product, sale or order not on this store

404

device_not_found

A till id that is not this till on this store

404

media_not_found

Photo ticket unknown or expired

409

already_exists

A document with this external_id is recorded; its id is in details.id

409

order_claimed

Another till claimed the order

409

claim_required

Moving an order this till has not claimed

409

transition_not_allowed

The move is not allowed from the order's status

410

device_revoked

The till was disconnected

413

payload_too_large

Body larger than 1 MiB

422

validation_error

A field is wrong; details.field names it

422

media_mismatch

The uploaded photo does not match its ticket

422

idempotency_key_reuse

Same Idempotency-Key with a different body

429

rate_limited

Over a limit; wait for Retry-After

501

not_implemented

The backup paths

503

storage_unavailable

Photo storage is not reachable; retry later

Did this answer your question?