Sales, Stock & Till
The full point-of-sale layer on top of payments: a product catalog,
barcode-driven stock control, sales settled by cash or InnBucks, and daily
reports β plus a bundled Till app at /till that needs zero code. Everything the
till does is also plain REST, so any custom frontend can do the same.
The model β three objects
Product
Name, price, barcode, optional stock tracking. A tracked good (bread), an untracked service (taxi fare), or price-at-sale (price_cents: null).
Stock move
Append-only journal: receive, sale, adjust, count. On-hand is always explainable, like the money ledger.
Sale
A basket of line items + a tender. Cash completes instantly; InnBucks completes when its payment settles. Stock decrements atomically with completion.
The catalog and on-hand stock are shared between test and live (the shelf is the shelf), but
only live sales change on-hand quantities β test-mode sales journal their movements with
livemode: false and never bend real inventory. Train staff in TEST without consequences.
Catalog
curl https://omniway.online/v1/products \
-H "Authorization: Bearer sk_live_..." -H "Content-Type: application/json" \
-d '{
"name": "Bread 700g",
"barcode": "6001234567890",
"price_cents": 150,
"track_stock": true,
"stock_qty": 20,
"low_stock_at": 5
}'
barcodeis unique per merchant β the scanner contract.409 barcode_takenon duplicates.price_cents: null= price decided at sale time (hardware off-cuts, taxi fares, consultations).track_stock: falsefor services β sellable, never counted.- Soft-delete with
PATCH {"active": false}; history stays intact.
Search/list: GET /v1/products?q=bread, exact scanner lookup:
GET /v1/products?barcode=6001234567890.
Barcode scanners (and other capture)
USB and Bluetooth barcode scanners are keyboards β they type the code and press Enter. No drivers, no SDK:
scanBox.addEventListener("keydown", async (e) => {
if (e.key !== "Enter") return;
const code = scanBox.value.trim(); scanBox.value = "";
const { data } = await till.get(`/products?barcode=${encodeURIComponent(code)}`);
data.length ? cart.add(data[0]) : showNoMatch(code);
});
The bundled Till app works exactly this way β keep its scan box focused and scan away: scan-to-sell on the Sell screen, scan-to-receive on the Stock screen. The same pattern accepts any keyboard-wedge input device (membrane keypads, QR wands, magstripe readers in keyboard mode).
Stock control
# goods in from a supplier
curl β¦/v1/stock/receive -d '{"product_id":"β¦","qty":24,"note":"Delivery β Lobels"}'
# damage/shrinkage correction (signed delta)
curl β¦/v1/stock/adjust -d '{"product_id":"β¦","qty":-2,"note":"damaged"}'
# stocktake: set the counted absolute quantity
curl β¦/v1/stock/count -d '{"product_id":"β¦","qty":18,"note":"monthly count"}'
Every change is a journal row (GET /v1/stock/moves) β who/when/why for any discrepancy.
GET /v1/stock/report returns on-hand units, valuation at current prices, and the low-stock list
(products at or below their low_stock_at).
Sales
curl https://omniway.online/v1/sales \
-H "Authorization: Bearer sk_live_..." -H "Content-Type: application/json" \
-H "Idempotency-Key: till3-0611-0142" \
-d '{
"tender": "cash",
"items": [
{"product_id": "β¦breadβ¦", "qty": 2},
{"name": "Delivery", "qty": 1, "unit_cents": 100}
],
"cashier": "Rudo"
}'
# β status:"completed" immediately; tracked stock decremented
Line items are {product_id, qty} (price from the catalog, overridable with
unit_cents) or fully ad-hoc {name, qty, unit_cents}. Items snapshot name and price
at sale time β later catalog edits never rewrite history.
curl β¦ -d '{"tender": "innbucks", "items": [{"product_id": "β¦cokeβ¦", "qty": 2}]}'
# β status:"pending" + embedded payment { code, qr_png_data_url, deep_link, checkout_url }
# show the code/QR; the sale completes itself when the customer pays
# (poll GET /v1/sales/:id, or act on the sale.completed webhook)
Cash sales count in takings and decrement stock, but never touch your payable balance β cash in the drawer is already yours. Only InnBucks sales feed the balance you can pay out. The daily report separates the two tenders for drawer reconciliation.
If an InnBucks payment expires unpaid, the sale auto-cancels (status: "cancelled") β no stock
moves, no money. Ring it up again for another attempt.
Reports
curl ".../v1/sales/report?from=2026-06-11T00:00:00Z" -H "Authorization: Bearer sk_live_..."
# β by_tender: cash vs innbucks count + totals per currency
# top_products: best sellers by revenue
# totals: overall count + per-currency takings
Pair with GET /v1/stock/report for the full picture: what sold, what it took, what's left and
what it's worth. Money-side reporting (fees, payouts, statement CSV) stays on the
ledger.
The bundled Till app
https://omniway.online/till β sign in with your dashboard credentials. Three
screens, phone-and-tablet friendly, no installation:
| Screen | What it does |
|---|---|
| π Sell | Always-focused scan box (scanner or type-to-search), quick-tap product tiles, the cart with Β± quantities, then Cash (records instantly) or InnBucks (shows code + QR in a dialog and flips to β PAID on settlement). |
| π¦ Stock | Scan-to-receive, stocktake counts, new-product capture (scan an unknown barcode β β+ Newβ pre-fills it), low-stock alerts, on-hand valuation. |
| π Today | Today's takings by tender, top products, recent sales β the drawer-reconciliation view. |
The TEST/LIVE pill in the header switches worlds β train new staff in TEST, where sales are simulated and
stock is untouched. The till speaks to the same API documented above; if it ever feels limiting, build your
own frontend against /v1 and keep the data.
Industry presets β same engine, different habits
| Business | How to set up the catalog |
|---|---|
| Retail / tuck shop | Tracked products with barcodes and low-stock alerts; scan-to-sell; cash + InnBucks tenders. |
| Hardware store | Barcoded lines tracked; cut-to-size and bulk items as price-at-sale products (price_cents: null). |
| Taxi / transport | Untracked βFareβ products per route (fixed price) or one price-at-sale βFareβ; InnBucks tender shows the QR to the passenger. |
| Hospitality | Menu items untracked (or track bottled stock only); ad-hoc lines for specials; cashier name per sale for shift accountability. |
| Services / clinic | Untracked service products (consultation, procedure); price overrides per sale where needed. |